1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License, Version 1.0 only
   6  * (the "License").  You may not use this file except in compliance
   7  * with the License.
   8  *
   9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  10  * or http://www.opensolaris.org/os/licensing.
  11  * See the License for the specific language governing permissions
  12  * and limitations under the License.
  13  *
  14  * When distributing Covered Code, include this CDDL HEADER in each
  15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  16  * If applicable, add the following below this CDDL HEADER, with the
  17  * fields enclosed by brackets "[]" replaced with your own identifying
  18  * information: Portions Copyright [yyyy] [name of copyright owner]
  19  *
  20  * CDDL HEADER END
  21  */
  22 /*
  23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  */
  26 
  27 #pragma ident   "%Z%%M% %I%     %E% SMI"
  28 
  29 /*
  30  * This file contains public functions for managing legacy DHCP network
  31  * containers.  For the semantics of these functions, please see the
  32  * Enterprise DHCP Architecture Document.
  33  */
  34 
  35 #include <alloca.h>
  36 #include <arpa/inet.h>
  37 #include <ctype.h>
  38 #include <dhcp_svc_public.h>
  39 #include <dirent.h>
  40 #include <errno.h>
  41 #include <fcntl.h>
  42 #include <libgen.h>
  43 #include <libinetutil.h>
  44 #include <netinet/in.h>
  45 #include <stdlib.h>
  46 #include <string.h>
  47 #include <sys/socket.h>
  48 #include <sys/stat.h>
  49 #include <sys/types.h>
  50 #include <unistd.h>
  51 
  52 #include "dhcp_network.h"
  53 #include "util.h"
  54 
  55 static void net2path(char *, size_t, const char *, ipaddr_t, const char *);
  56 static boolean_t record_match(char *[], dn_rec_t *, const dn_rec_t *, uint_t);
  57 static int write_rec(int, dn_rec_t *, off_t);
  58 
  59 /* ARGSUSED */
  60 int
  61 open_dn(void **handlep, const char *location, uint_t flags,
  62     const struct in_addr *netp, const struct in_addr *maskp)
  63 {
  64         char            dnpath[MAXPATHLEN];
  65         dn_handle_t     *dhp;
  66         int             retval;
  67         int             fd;
  68 
  69         dhp = malloc(sizeof (dn_handle_t));
  70         if (dhp == NULL)
  71                 return (DSVC_NO_MEMORY);
  72 
  73         dhp->dh_net = netp->s_addr;
  74         dhp->dh_oflags = flags;
  75         (void) strlcpy(dhp->dh_location, location, MAXPATHLEN);
  76 
  77         /*
  78          * This is a legacy format which has no header, so we neither write
  79          * nor verify a header (we just create the file or make sure it
  80          * exists, depending on the value of `flags').
  81          */
  82         net2path(dnpath, MAXPATHLEN, location, netp->s_addr, "");
  83         retval = open_file(dnpath, flags, &fd);
  84         if (retval != DSVC_SUCCESS) {
  85                 free(dhp);
  86                 return (retval);
  87         }
  88         (void) close(fd);
  89 
  90         *handlep = dhp;
  91         return (DSVC_SUCCESS);
  92 }
  93 
  94 int
  95 close_dn(void **handlep)
  96 {
  97         free(*handlep);
  98         return (DSVC_SUCCESS);
  99 }
 100 
 101 int
 102 remove_dn(const char *dir, const struct in_addr *netp)
 103 {
 104         char dnpath[MAXPATHLEN];
 105 
 106         net2path(dnpath, MAXPATHLEN, dir, netp->s_addr, "");
 107         if (unlink(dnpath) == -1)
 108                 return (syserr_to_dsvcerr(errno));
 109 
 110         return (DSVC_SUCCESS);
 111 }
 112 
 113 static int
 114 find_dn(FILE *fp, uint_t flags, uint_t query, int count,
 115     const dn_rec_t *targetp, dn_rec_list_t **recordsp, uint_t *nrecordsp)
 116 {
 117         int             retval = DSVC_SUCCESS;
 118         char            *commentp, *fields[DNF_MAX_FIELDS];
 119         char            *buf = NULL;
 120         uint_t          nrecords;
 121         dn_rec_t        dn, *recordp;
 122         dn_rec_list_t   *records, *new_records;
 123         unsigned int    nfields;
 124         off_t           recoff;
 125 
 126         if (fseek(fp, 0, SEEK_SET) == -1)
 127                 return (DSVC_INTERNAL);
 128 
 129         records = NULL;
 130         for (nrecords = 0; count < 0 || nrecords < count; ) {
 131                 free(buf);
 132 
 133                 if (flags & FIND_POSITION)
 134                         recoff = ftello(fp);
 135 
 136                 buf = read_entry(fp);
 137                 if (buf == NULL) {
 138                         if (!feof(fp))
 139                                 retval = DSVC_NO_MEMORY;
 140                         break;
 141                 }
 142 
 143                 /*
 144                  * Skip pure comment lines; for now this just skips the
 145                  * header information at the top of the container.
 146                  */
 147                 if (buf[0] == DNF_COMMENT_CHAR)
 148                         continue;
 149 
 150                 /*
 151                  * Tell field_split() that there's one less field than
 152                  * there really is.  We do this so that the comment and the
 153                  * macro field both end up in the DNF_MACRO field, since
 154                  * both fields are optional and it requires some fancy
 155                  * footwork (below) to tell which (if any) the record
 156                  * contains.
 157                  */
 158                 nfields = field_split(buf, DNF_MAX_FIELDS - 1, fields, " \t");
 159                 if (nfields < DNF_REQ_FIELDS)
 160                         continue;
 161 
 162                 if (nfields == DNF_REQ_FIELDS) {
 163                         fields[DNF_MACRO] = "";
 164                         fields[DNF_COMMENT] = "";
 165                 } else {
 166                         /*
 167                          * Assume there is a comment; if we hit a comment
 168                          * delimiter char (DNF_COMMENT_CHAR), then simply
 169                          * change it to a NUL and advance commentp.  If we
 170                          * hit whitespace, replace the first instance with
 171                          * NUL, and go searching for DNF_COMMENT_CHAR.
 172                          * This step is important since it efficiently
 173                          * handles the common case where a comment is
 174                          * preceded by a space.
 175                          */
 176                         commentp = fields[DNF_MACRO];
 177                         while (!isspace(*commentp) &&
 178                             *commentp != DNF_COMMENT_CHAR && *commentp != '\0')
 179                                 commentp++;
 180 
 181                         if (isspace(*commentp)) {
 182                                 *commentp++ = '\0';
 183                                 commentp = strchr(commentp, DNF_COMMENT_CHAR);
 184                                 if (commentp == NULL)
 185                                         commentp = "";
 186                         }
 187 
 188                         if (*commentp == DNF_COMMENT_CHAR)
 189                                 *commentp++ = '\0';
 190 
 191                         fields[DNF_COMMENT] = commentp;
 192                 }
 193 
 194                 /*
 195                  * See if we've got a match, filling in dnf.dnf_rec as
 196                  * we go.  If record_match() succeeds, dnf.dnf_rec will
 197                  * be completely filled in.
 198                  */
 199                 if (!record_match(fields, &dn, targetp, query))
 200                         continue;
 201 
 202                 /*
 203                  * Caller just wants a count of the number of matching
 204                  * records, not the records themselves; continue.
 205                  */
 206                 if (recordsp == NULL) {
 207                         nrecords++;
 208                         continue;
 209                 }
 210 
 211                 /*
 212                  * Allocate record; if FIND_POSITION flag is set, then
 213                  * we need to allocate an extended (dn_recpos_t) record.
 214                  */
 215                 if (flags & FIND_POSITION)
 216                         recordp = malloc(sizeof (dn_recpos_t));
 217                 else
 218                         recordp = malloc(sizeof (dn_rec_t));
 219 
 220                 if (recordp == NULL) {
 221                         if ((flags & FIND_PARTIAL) == 0)
 222                                 retval = DSVC_NO_MEMORY;
 223                         break;
 224                 }
 225 
 226                 /*
 227                  * Fill in record; do a structure copy from our automatic
 228                  * dn.  If FIND_POSITION flag is on, pass back additional
 229                  * position information.
 230                  */
 231                 *recordp = dn;
 232                 if (flags & FIND_POSITION) {
 233                         ((dn_recpos_t *)recordp)->dnp_off = recoff;
 234                         ((dn_recpos_t *)recordp)->dnp_size = ftello(fp) -
 235                             recoff;
 236                 }
 237 
 238                 /*
 239                  * Chuck the record on the list and up the counter.
 240                  */
 241                 new_records = add_dnrec_to_list(recordp, records);
 242                 if (new_records == NULL) {
 243                         free(recordp);
 244                         if ((flags & FIND_PARTIAL) == 0)
 245                                 retval = DSVC_NO_MEMORY;
 246                         break;
 247                 }
 248 
 249                 records = new_records;
 250                 nrecords++;
 251         }
 252 
 253         free(buf);
 254 
 255         if (retval == DSVC_SUCCESS) {
 256                 *nrecordsp = nrecords;
 257                 if (recordsp != NULL)
 258                         *recordsp = records;
 259                 return (DSVC_SUCCESS);
 260         }
 261 
 262         if (records != NULL)
 263                 free_dnrec_list(records);
 264 
 265         return (retval);
 266 }
 267 
 268 int
 269 lookup_dn(void *handle, boolean_t partial, uint_t query, int count,
 270     const dn_rec_t *targetp, dn_rec_list_t **recordsp, uint_t *nrecordsp)
 271 {
 272         int             retval;
 273         char            dnpath[MAXPATHLEN];
 274         FILE            *fp;
 275         dn_handle_t     *dhp = (dn_handle_t *)handle;
 276 
 277         if ((dhp->dh_oflags & DSVC_READ) == 0)
 278                 return (DSVC_ACCESS);
 279 
 280         net2path(dnpath, MAXPATHLEN, dhp->dh_location, dhp->dh_net, "");
 281         fp = fopen(dnpath, "r");
 282         if (fp == NULL)
 283                 return (syserr_to_dsvcerr(errno));
 284 
 285         retval = find_dn(fp, partial ? FIND_PARTIAL : 0, query, count, targetp,
 286             recordsp, nrecordsp);
 287 
 288         (void) fclose(fp);
 289         return (retval);
 290 }
 291 
 292 /*
 293  * Compares the fields in fields[] agains the fields in target `targetp',
 294  * using `query' to decide what fields to compare.  Returns B_TRUE if `dnp'
 295  * matches `targetp', B_FALSE if not.  On success, `dnp' is completely
 296  * filled in.
 297  */
 298 static boolean_t
 299 record_match(char *fields[], dn_rec_t *dnp, const dn_rec_t *targetp,
 300     uint_t query)
 301 {
 302         unsigned int    qflags[] = { DN_QFDYNAMIC, DN_QFAUTOMATIC, DN_QFMANUAL,
 303                                     DN_QFUNUSABLE, DN_QFBOOTP_ONLY };
 304         unsigned int    flags[]  = { DN_FDYNAMIC, DN_FAUTOMATIC, DN_FMANUAL,
 305                                     DN_FUNUSABLE, DN_FBOOTP_ONLY };
 306         unsigned int    i;
 307         uint_t          dn_cid_len;
 308 
 309         dnp->dn_cip.s_addr = ntohl(inet_addr(fields[DNF_CIP]));
 310         if (DSVC_QISEQ(query, DN_QCIP) &&
 311             dnp->dn_cip.s_addr != targetp->dn_cip.s_addr)
 312                 return (B_FALSE);
 313         if (DSVC_QISNEQ(query, DN_QCIP) &&
 314             dnp->dn_cip.s_addr == targetp->dn_cip.s_addr)
 315                 return (B_FALSE);
 316 
 317         dnp->dn_lease = atoi(fields[DNF_LEASE]);
 318         if (DSVC_QISEQ(query, DN_QLEASE) && targetp->dn_lease != dnp->dn_lease)
 319                 return (B_FALSE);
 320         if (DSVC_QISNEQ(query, DN_QLEASE) && targetp->dn_lease == dnp->dn_lease)
 321                 return (B_FALSE);
 322 
 323         /*
 324          * We use dn_cid_len since dnp->dn_cid_len is of type uchar_t but
 325          * hexascii_to_octet() expects a uint_t *
 326          */
 327         dn_cid_len = DN_MAX_CID_LEN;
 328         if (hexascii_to_octet(fields[DNF_CID], strlen(fields[DNF_CID]),
 329             dnp->dn_cid, &dn_cid_len) != 0)
 330                 return (B_FALSE);
 331 
 332         dnp->dn_cid_len = dn_cid_len;
 333         if (DSVC_QISEQ(query, DN_QCID) &&
 334             (dnp->dn_cid_len != targetp->dn_cid_len ||
 335             (memcmp(dnp->dn_cid, targetp->dn_cid, dnp->dn_cid_len) != 0)))
 336                 return (B_FALSE);
 337         if (DSVC_QISNEQ(query, DN_QCID) &&
 338             (dnp->dn_cid_len == targetp->dn_cid_len &&
 339             (memcmp(dnp->dn_cid, targetp->dn_cid, dnp->dn_cid_len) == 0)))
 340                 return (B_FALSE);
 341 
 342         dnp->dn_sip.s_addr = ntohl(inet_addr(fields[DNF_SIP]));
 343         if (DSVC_QISEQ(query, DN_QSIP) &&
 344             dnp->dn_sip.s_addr != targetp->dn_sip.s_addr)
 345                 return (B_FALSE);
 346         if (DSVC_QISNEQ(query, DN_QSIP) &&
 347             dnp->dn_sip.s_addr == targetp->dn_sip.s_addr)
 348                 return (B_FALSE);
 349 
 350         (void) strlcpy(dnp->dn_macro, fields[DNF_MACRO],
 351             sizeof (dnp->dn_macro));
 352         if (DSVC_QISEQ(query, DN_QMACRO) &&
 353             strcmp(targetp->dn_macro, dnp->dn_macro) != 0)
 354                 return (B_FALSE);
 355         if (DSVC_QISNEQ(query, DN_QMACRO) &&
 356             strcmp(targetp->dn_macro, dnp->dn_macro) == 0)
 357                 return (B_FALSE);
 358 
 359         dnp->dn_flags = atoi(fields[DNF_FLAGS]);
 360         for (i = 0; i < sizeof (qflags) / sizeof (unsigned int); i++) {
 361                 if (DSVC_QISEQ(query, qflags[i]) &&
 362                     (dnp->dn_flags & flags[i]) !=
 363                     (targetp->dn_flags & flags[i]))
 364                         return (B_FALSE);
 365                 if (DSVC_QISNEQ(query, qflags[i]) &&
 366                     (dnp->dn_flags & flags[i]) ==
 367                     (targetp->dn_flags & flags[i]))
 368                         return (B_FALSE);
 369         }
 370         (void) strlcpy(dnp->dn_comment, fields[DNF_COMMENT],
 371             sizeof (dnp->dn_comment));
 372 
 373         return (B_TRUE);
 374 }
 375 
 376 /*
 377  * Internal dhcp_network record update routine, used to factor out the
 378  * common code between add_dn(), delete_dn(), and modify_dn().  If `origp'
 379  * is NULL, then act like add_dn(); if `newp' is NULL, then act like
 380  * delete_dn(); otherwise act like modify_dn().
 381  */
 382 static int
 383 update_dn(const dn_handle_t *dhp, const dn_rec_t *origp, dn_rec_t *newp)
 384 {
 385         char            dnpath[MAXPATHLEN], newpath[MAXPATHLEN];
 386         int             retval = DSVC_SUCCESS;
 387         off_t           recoff, recnext;
 388         dn_rec_list_t   *reclist;
 389         FILE            *fp;
 390         int             newfd;
 391         uint_t          found;
 392         int             query;
 393         struct stat     st;
 394 
 395         if ((dhp->dh_oflags & DSVC_WRITE) == 0)
 396                 return (DSVC_ACCESS);
 397 
 398         /*
 399          * Open the container to update and a new container file which we
 400          * will store the updated version of the container in.  When the
 401          * update is done, rename the new file to be the real container.
 402          */
 403         net2path(dnpath, MAXPATHLEN, dhp->dh_location, dhp->dh_net, "");
 404         fp = fopen(dnpath, "r");
 405         if (fp == NULL)
 406                 return (syserr_to_dsvcerr(errno));
 407 
 408         net2path(newpath, MAXPATHLEN, dhp->dh_location, dhp->dh_net, ".new");
 409         newfd = open(newpath, O_CREAT|O_TRUNC|O_WRONLY, 0644);
 410         if (newfd == -1) {
 411                 (void) fclose(fp);
 412                 return (syserr_to_dsvcerr(errno));
 413         }
 414 
 415         DSVC_QINIT(query);
 416         DSVC_QEQ(query, DN_QCIP);
 417 
 418         /*
 419          * If we're adding a new record or changing a key for an existing
 420          * record, bail if the record we want to add already exists.
 421          */
 422         if (newp != NULL) {
 423                 if (origp == NULL ||
 424                     origp->dn_cip.s_addr != newp->dn_cip.s_addr) {
 425                         retval = find_dn(fp, 0, query, 1, newp, NULL, &found);
 426                         if (retval != DSVC_SUCCESS)
 427                                 goto out;
 428                         if (found != 0) {
 429                                 retval = DSVC_EXISTS;
 430                                 goto out;
 431                         }
 432                 }
 433         }
 434 
 435         /*
 436          * If we're deleting or modifying record, make sure the record
 437          * still exists.  Note that we don't check signatures because this
 438          * is a legacy format that has no signatures.
 439          */
 440         if (origp != NULL) {
 441                 retval = find_dn(fp, FIND_POSITION, query, 1, origp, &reclist,
 442                     &found);
 443                 if (retval != DSVC_SUCCESS)
 444                         goto out;
 445                 if (found == 0) {
 446                         retval = DSVC_NOENT;
 447                         goto out;
 448                 }
 449 
 450                 /*
 451                  * Note the offset of the record we're modifying or deleting
 452                  * for use down below.
 453                  */
 454                 recoff  = ((dn_recpos_t *)reclist->dnl_rec)->dnp_off;
 455                 recnext = recoff + ((dn_recpos_t *)reclist->dnl_rec)->dnp_size;
 456 
 457                 free_dnrec_list(reclist);
 458         } else {
 459                 /*
 460                  * No record to modify or delete, so set `recoff' and
 461                  * `recnext' appropriately.
 462                  */
 463                 recoff = 0;
 464                 recnext = 0;
 465         }
 466 
 467         /*
 468          * Make a new copy of the container.  If we're deleting or
 469          * modifying a record, don't copy that record to the new container.
 470          */
 471         if (fstat(fileno(fp), &st) == -1) {
 472                 retval = DSVC_INTERNAL;
 473                 goto out;
 474         }
 475 
 476         retval = copy_range(fileno(fp), 0, newfd, 0, recoff);
 477         if (retval != DSVC_SUCCESS)
 478                 goto out;
 479 
 480         retval = copy_range(fileno(fp), recnext, newfd, recoff,
 481             st.st_size - recnext);
 482         if (retval != DSVC_SUCCESS)
 483                 goto out;
 484 
 485         /*
 486          * If there's a new/modified record, append it to the new container.
 487          */
 488         if (newp != NULL) {
 489                 retval = write_rec(newfd, newp, recoff + st.st_size - recnext);
 490                 if (retval != DSVC_SUCCESS)
 491                         goto out;
 492         }
 493 
 494         /*
 495          * Note: we close these descriptors before the rename(2) (rather
 496          * than just having the `out:' label clean them up) to save NFS
 497          * some work (otherwise, NFS has to save `dnpath' to an alternate
 498          * name since its vnode would still be active).
 499          */
 500         (void) fclose(fp);
 501         (void) close(newfd);
 502 
 503         if (rename(newpath, dnpath) == -1)
 504                 retval = syserr_to_dsvcerr(errno);
 505 
 506         return (retval);
 507 out:
 508         (void) fclose(fp);
 509         (void) close(newfd);
 510         (void) unlink(newpath);
 511         return (retval);
 512 }
 513 
 514 int
 515 add_dn(void *handle, dn_rec_t *addp)
 516 {
 517         return (update_dn((dn_handle_t *)handle, NULL, addp));
 518 }
 519 
 520 int
 521 modify_dn(void *handle, const dn_rec_t *origp, dn_rec_t *newp)
 522 {
 523         return (update_dn((dn_handle_t *)handle, origp, newp));
 524 }
 525 
 526 int
 527 delete_dn(void *handle, const dn_rec_t *delp)
 528 {
 529         return (update_dn((dn_handle_t *)handle, delp, NULL));
 530 }
 531 
 532 int
 533 list_dn(const char *location, char ***listppp, uint_t *countp)
 534 {
 535         char            ipaddr[INET_ADDRSTRLEN];
 536         struct dirent   *result;
 537         DIR             *dirp;
 538         unsigned int    i, count = 0;
 539         char            *re, **new_listpp, **listpp = NULL;
 540         int             error;
 541 
 542         dirp = opendir(location);
 543         if (dirp == NULL) {
 544                 switch (errno) {
 545                 case EACCES:
 546                 case EPERM:
 547                         return (DSVC_ACCESS);
 548                 case ENOENT:
 549                         return (DSVC_NO_LOCATION);
 550                 default:
 551                         break;
 552                 }
 553                 return (DSVC_INTERNAL);
 554         }
 555 
 556         /*
 557          * Compile a regular expression matching an IP address delimited by
 558          * underscores. Note that the `$0' at the end allows us to save the
 559          * IP address in ipaddr when calling regex(3C).
 560          */
 561         re = regcmp("^(([0-9]{1,3}\\_){3}[0-9]{1,3})$0$", (char *)0);
 562         if (re == NULL)
 563                 return (DSVC_NO_MEMORY);
 564 
 565         while ((result = readdir(dirp)) != NULL) {
 566                 if (regex(re, result->d_name, ipaddr) != NULL) {
 567                         new_listpp = realloc(listpp,
 568                             (sizeof (char **)) * (count + 1));
 569                         if (new_listpp == NULL) {
 570                                 error = DSVC_NO_MEMORY;
 571                                 goto fail;
 572                         }
 573                         listpp = new_listpp;
 574                         listpp[count] = strdup(ipaddr);
 575                         if (listpp[count] == NULL) {
 576                                 error = DSVC_NO_MEMORY;
 577                                 goto fail;
 578                         }
 579 
 580                         /*
 581                          * Change all underscores to dots.
 582                          */
 583                         for (i = 0; listpp[count][i] != '\0'; i++) {
 584                                 if (listpp[count][i] == '_')
 585                                         listpp[count][i] = '.';
 586                         }
 587 
 588                         count++;
 589                 }
 590         }
 591         free(re);
 592         (void) closedir(dirp);
 593 
 594         *countp = count;
 595         *listppp = listpp;
 596         return (DSVC_SUCCESS);
 597 
 598 fail:
 599         free(re);
 600         (void) closedir(dirp);
 601 
 602         for (i = 0; i < count; i++)
 603                 free(listpp[i]);
 604         free(listpp);
 605         return (error);
 606 }
 607 
 608 /*
 609  * Given a buffer `path' of `pathlen' bytes, fill it in with a path to the
 610  * DHCP Network table for IP network `ip' located in directory `dir' with a
 611  * suffix of `suffix'.
 612  */
 613 static void
 614 net2path(char *path, size_t pathlen, const char *dir, ipaddr_t ip,
 615     const char *suffix)
 616 {
 617         (void) snprintf(path, pathlen, "%s/%d_%d_%d_%d%s", dir, ip >> 24,
 618             (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff, suffix);
 619 }
 620 
 621 /*
 622  * Write the dn_rec_t `recp' into the open container `fd' at offset
 623  * `recoff'.  Returns DSVC_* error code.
 624  */
 625 static int
 626 write_rec(int fd, dn_rec_t *recp, off_t recoff)
 627 {
 628         char            entbuf[1024], *ent = entbuf;
 629         size_t          entsize = sizeof (entbuf);
 630         int             entlen;
 631         char            dn_cip[INET_ADDRSTRLEN], dn_sip[INET_ADDRSTRLEN];
 632         char            dn_cid[DN_MAX_CID_LEN * 2 + 1];
 633         unsigned int    dn_cid_len = sizeof (dn_cid);
 634         struct in_addr  nip;
 635 
 636         if (octet_to_hexascii(recp->dn_cid, recp->dn_cid_len, dn_cid,
 637             &dn_cid_len) != 0)
 638                 return (DSVC_INTERNAL);
 639 
 640         nip.s_addr = htonl(recp->dn_cip.s_addr);
 641         (void) inet_ntop(AF_INET, &nip, dn_cip, sizeof (dn_cip));
 642         nip.s_addr = htonl(recp->dn_sip.s_addr);
 643         (void) inet_ntop(AF_INET, &nip, dn_sip, sizeof (dn_sip));
 644 again:
 645         if (recp->dn_comment[0] != '\0') {
 646                 entlen = snprintf(ent, entsize, "%s %02hu %s %s %u %s %c%s\n",
 647                     dn_cid, recp->dn_flags, dn_cip, dn_sip, recp->dn_lease,
 648                     recp->dn_macro, DNF_COMMENT_CHAR, recp->dn_comment);
 649         } else {
 650                 entlen = snprintf(ent, entsize, "%s %02hu %s %s %u %s\n",
 651                     dn_cid, recp->dn_flags, dn_cip, dn_sip, recp->dn_lease,
 652                     recp->dn_macro);
 653         }
 654 
 655         if (entlen == -1)
 656                 return (syserr_to_dsvcerr(errno));
 657 
 658         if (entlen > entsize) {
 659                 entsize = entlen;
 660                 ent = alloca(entlen);
 661                 goto again;
 662         }
 663 
 664         if (pnwrite(fd, ent, entlen, recoff) == -1)
 665                 return (syserr_to_dsvcerr(errno));
 666 
 667         return (DSVC_SUCCESS);
 668 }