xref: /onnv-gate/usr/src/lib/libdhcpsvc/modules/files0/dhcp_network.c (revision 871:f917b2ed7d21)
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
open_dn(void ** handlep,const char * location,uint_t flags,const struct in_addr * netp,const struct in_addr * maskp)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
close_dn(void ** handlep)95 close_dn(void **handlep)
96 {
97 	free(*handlep);
98 	return (DSVC_SUCCESS);
99 }
100 
101 int
remove_dn(const char * dir,const struct in_addr * netp)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
find_dn(FILE * fp,uint_t flags,uint_t query,int count,const dn_rec_t * targetp,dn_rec_list_t ** recordsp,uint_t * nrecordsp)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
lookup_dn(void * handle,boolean_t partial,uint_t query,int count,const dn_rec_t * targetp,dn_rec_list_t ** recordsp,uint_t * nrecordsp)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
record_match(char * fields[],dn_rec_t * dnp,const dn_rec_t * targetp,uint_t query)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
update_dn(const dn_handle_t * dhp,const dn_rec_t * origp,dn_rec_t * newp)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
add_dn(void * handle,dn_rec_t * addp)515 add_dn(void *handle, dn_rec_t *addp)
516 {
517 	return (update_dn((dn_handle_t *)handle, NULL, addp));
518 }
519 
520 int
modify_dn(void * handle,const dn_rec_t * origp,dn_rec_t * newp)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
delete_dn(void * handle,const dn_rec_t * delp)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
list_dn(const char * location,char *** listppp,uint_t * countp)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
net2path(char * path,size_t pathlen,const char * dir,ipaddr_t ip,const char * suffix)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
write_rec(int fd,dn_rec_t * recp,off_t recoff)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 }
669