xref: /titanic_41/usr/src/cmd/cmd-inet/sbin/dhcpagent/util.c (revision c2580b931007758eab8cb5ae8726ebe1588e259b)
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 (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <unistd.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <stdlib.h>
32 #include <netinet/in.h>		/* struct in_addr */
33 #include <netinet/dhcp.h>
34 #include <signal.h>
35 #include <sys/dlpi.h>
36 #include <sys/socket.h>
37 #include <net/route.h>
38 #include <net/if_arp.h>
39 #include <string.h>
40 #include <dhcpmsg.h>
41 #include <ctype.h>
42 #include <netdb.h>
43 #include <fcntl.h>
44 #include <stdio.h>
45 
46 #include "states.h"
47 #include "agent.h"
48 #include "interface.h"
49 #include "util.h"
50 #include "packet.h"
51 
52 /*
53  * this file contains utility functions that have no real better home
54  * of their own.  they can largely be broken into six categories:
55  *
56  *  o  conversion functions -- functions to turn integers into strings,
57  *     or to convert between units of a similar measure.
58  *
59  *  o  ipc-related functions -- functions to simplify the generation of
60  *     ipc messages to the agent's clients.
61  *
62  *  o  signal-related functions -- functions to clean up the agent when
63  *     it receives a signal.
64  *
65  *  o  routing table manipulation functions
66  *
67  *  o  acknak handler functions
68  *
69  *  o  true miscellany -- anything else
70  */
71 
72 /*
73  * pkt_type_to_string(): stringifies a packet type
74  *
75  *   input: uchar_t: a DHCP packet type value, as defined in RFC2131
76  *  output: const char *: the stringified packet type
77  */
78 
79 const char *
80 pkt_type_to_string(uchar_t type)
81 {
82 	/*
83 	 * note: the ordering here allows direct indexing of the table
84 	 *	 based on the RFC2131 packet type value passed in.
85 	 */
86 
87 	static const char *types[] = {
88 		"BOOTP",  "DISCOVER", "OFFER",   "REQUEST", "DECLINE",
89 		"ACK",    "NAK",      "RELEASE", "INFORM"
90 	};
91 
92 	if (type >= (sizeof (types) / sizeof (*types)) || types[type] == NULL)
93 		return ("<unknown>");
94 
95 	return (types[type]);
96 }
97 
98 /*
99  * dlpi_to_arp(): converts DLPI datalink types into ARP datalink types
100  *
101  *   input: uchar_t: the DLPI datalink type
102  *  output: uchar_t: the ARP datalink type (0 if no corresponding code)
103  */
104 
105 uchar_t
106 dlpi_to_arp(uchar_t dlpi_type)
107 {
108 	switch (dlpi_type) {
109 
110 	case DL_ETHER:
111 		return (1);
112 
113 	case DL_FRAME:
114 		return (15);
115 
116 	case DL_ATM:
117 		return (16);
118 
119 	case DL_HDLC:
120 		return (17);
121 
122 	case DL_FC:
123 		return (18);
124 
125 	case DL_CSMACD:				/* ieee 802 networks */
126 	case DL_TPB:
127 	case DL_TPR:
128 	case DL_METRO:
129 	case DL_FDDI:
130 		return (6);
131 	case DL_IB:
132 		return (ARPHRD_IB);
133 	}
134 
135 	return (0);
136 }
137 
138 /*
139  * monosec_to_string(): converts a monosec_t into a date string
140  *
141  *   input: monosec_t: the monosec_t to convert
142  *  output: const char *: the corresponding date string
143  */
144 
145 const char *
146 monosec_to_string(monosec_t monosec)
147 {
148 	time_t	time = monosec_to_time(monosec);
149 	char	*time_string = ctime(&time);
150 
151 	/* strip off the newline -- ugh, why, why, why.. */
152 	time_string[strlen(time_string) - 1] = '\0';
153 	return (time_string);
154 }
155 
156 /*
157  * monosec(): returns a monotonically increasing time in seconds that
158  *            is not affected by stime(2) or adjtime(2).
159  *
160  *   input: void
161  *  output: monosec_t: the number of seconds since some time in the past
162  */
163 
164 monosec_t
165 monosec(void)
166 {
167 	return (gethrtime() / NANOSEC);
168 }
169 
170 /*
171  * monosec_to_time(): converts a monosec_t into real wall time
172  *
173  *    input: monosec_t: the absolute monosec_t to convert
174  *   output: time_t: the absolute time that monosec_t represents in wall time
175  */
176 
177 time_t
178 monosec_to_time(monosec_t abs_monosec)
179 {
180 	return (abs_monosec - monosec()) + time(NULL);
181 }
182 
183 /*
184  * send_ok_reply(): sends an "ok" reply to a request and closes the ipc
185  *		    connection
186  *
187  *   input: dhcp_ipc_request_t *: the request to reply to
188  *	    int *: the ipc connection file descriptor (set to -1 on return)
189  *  output: void
190  *    note: the request is freed (thus the request must be on the heap).
191  */
192 
193 void
194 send_ok_reply(dhcp_ipc_request_t *request, int *control_fd)
195 {
196 	send_error_reply(request, 0, control_fd);
197 }
198 
199 /*
200  * send_error_reply(): sends an "error" reply to a request and closes the ipc
201  *		       connection
202  *
203  *   input: dhcp_ipc_request_t *: the request to reply to
204  *	    int: the error to send back on the ipc connection
205  *	    int *: the ipc connection file descriptor (set to -1 on return)
206  *  output: void
207  *    note: the request is freed (thus the request must be on the heap).
208  */
209 
210 void
211 send_error_reply(dhcp_ipc_request_t *request, int error, int *control_fd)
212 {
213 	send_data_reply(request, control_fd, error, DHCP_TYPE_NONE, NULL, NULL);
214 }
215 
216 /*
217  * send_data_reply(): sends a reply to a request and closes the ipc connection
218  *
219  *   input: dhcp_ipc_request_t *: the request to reply to
220  *	    int *: the ipc connection file descriptor (set to -1 on return)
221  *	    int: the status to send back on the ipc connection (zero for
222  *		 success, DHCP_IPC_E_* otherwise).
223  *	    dhcp_data_type_t: the type of the payload in the reply
224  *	    void *: the payload for the reply, or NULL if there is no payload
225  *	    size_t: the size of the payload
226  *  output: void
227  *    note: the request is freed (thus the request must be on the heap).
228  */
229 
230 void
231 send_data_reply(dhcp_ipc_request_t *request, int *control_fd,
232     int error, dhcp_data_type_t type, void *buffer, size_t size)
233 {
234 	dhcp_ipc_reply_t	*reply;
235 
236 	if (*control_fd == -1)
237 		return;
238 
239 	reply = dhcp_ipc_alloc_reply(request, error, buffer, size, type);
240 	if (reply == NULL)
241 		dhcpmsg(MSG_ERR, "send_data_reply: cannot allocate reply");
242 
243 	else if (dhcp_ipc_send_reply(*control_fd, reply) != 0)
244 		dhcpmsg(MSG_ERR, "send_data_reply: dhcp_ipc_send_reply");
245 
246 	/*
247 	 * free the request since we've now used it to send our reply.
248 	 * we can also close the socket since the reply has been sent.
249 	 */
250 
251 	free(reply);
252 	free(request);
253 	(void) dhcp_ipc_close(*control_fd);
254 	*control_fd = -1;
255 }
256 
257 /*
258  * print_server_msg(): prints a message from a DHCP server
259  *
260  *   input: struct ifslist *: the interface the message came in on
261  *	    DHCP_OPT *: the option containing the string to display
262  *  output: void
263  */
264 
265 void
266 print_server_msg(struct ifslist *ifsp, DHCP_OPT *p)
267 {
268 	dhcpmsg(MSG_INFO, "%s: message from server: %.*s", ifsp->if_name,
269 	    p->len, p->value);
270 }
271 
272 /*
273  * alrm_exit(): Signal handler for SIGARLM. terminates grandparent.
274  *
275  *    input: int: signal the handler was called with.
276  *
277  *   output: void
278  */
279 
280 static void
281 alrm_exit(int sig)
282 {
283 	int exitval;
284 
285 	if (sig == SIGALRM && grandparent != 0)
286 		exitval = EXIT_SUCCESS;
287 	else
288 		exitval = EXIT_FAILURE;
289 
290 	_exit(exitval);
291 }
292 
293 /*
294  * daemonize(): daemonizes the process
295  *
296  *   input: void
297  *  output: int: 1 on success, 0 on failure
298  */
299 
300 int
301 daemonize(void)
302 {
303 	/*
304 	 * We've found that adoption takes sufficiently long that
305 	 * a dhcpinfo run after dhcpagent -a is started may occur
306 	 * before the agent is ready to process the request.
307 	 * The result is an error message and an unhappy user.
308 	 *
309 	 * The initial process now sleeps for DHCP_ADOPT_SLEEP,
310 	 * unless interrupted by a SIGALRM, in which case it
311 	 * exits immediately. This has the effect that the
312 	 * grandparent doesn't exit until the dhcpagent is ready
313 	 * to process requests. This defers the the balance of
314 	 * the system start-up script processing until the
315 	 * dhcpagent is ready to field requests.
316 	 *
317 	 * grandparent is only set for the adopt case; other
318 	 * cases do not require the wait.
319 	 */
320 
321 	if (grandparent != 0)
322 		(void) signal(SIGALRM, alrm_exit);
323 
324 	switch (fork()) {
325 
326 	case -1:
327 		return (0);
328 
329 	case  0:
330 		if (grandparent != 0)
331 			(void) signal(SIGALRM, SIG_DFL);
332 
333 		/*
334 		 * setsid() makes us lose our controlling terminal,
335 		 * and become both a session leader and a process
336 		 * group leader.
337 		 */
338 
339 		(void) setsid();
340 
341 		/*
342 		 * under POSIX, a session leader can accidentally
343 		 * (through open(2)) acquire a controlling terminal if
344 		 * it does not have one.  just to be safe, fork again
345 		 * so we are not a session leader.
346 		 */
347 
348 		switch (fork()) {
349 
350 		case -1:
351 			return (0);
352 
353 		case 0:
354 			(void) signal(SIGHUP, SIG_IGN);
355 			(void) chdir("/");
356 			(void) umask(022);
357 			closefrom(0);
358 			break;
359 
360 		default:
361 			_exit(EXIT_SUCCESS);
362 		}
363 		break;
364 
365 	default:
366 		if (grandparent != 0) {
367 			(void) signal(SIGCHLD, SIG_IGN);
368 			dhcpmsg(MSG_DEBUG, "dhcpagent: daemonize: "
369 			    "waiting for adoption to complete.");
370 			if (sleep(DHCP_ADOPT_SLEEP) == 0) {
371 				dhcpmsg(MSG_WARNING, "dhcpagent: daemonize: "
372 				    "timed out awaiting adoption.");
373 			}
374 		}
375 		_exit(EXIT_SUCCESS);
376 	}
377 
378 	return (1);
379 }
380 
381 /*
382  * update_default_route(): update the interface's default route
383  *
384  *   input: int: the type of message; either RTM_ADD or RTM_DELETE
385  *	    struct in_addr: the default gateway to use
386  *	    const char *: the interface associated with the route
387  *	    int: any additional flags (besides RTF_STATIC and RTF_GATEWAY)
388  *  output: int: 1 on success, 0 on failure
389  */
390 
391 static int
392 update_default_route(const char *ifname, int type, struct in_addr *gateway_nbo,
393     int flags)
394 {
395 	struct {
396 		struct rt_msghdr	rm_mh;
397 		struct sockaddr_in	rm_dst;
398 		struct sockaddr_in	rm_gw;
399 		struct sockaddr_in	rm_mask;
400 		struct sockaddr_dl	rm_ifp;
401 	} rtmsg;
402 
403 	(void) memset(&rtmsg, 0, sizeof (rtmsg));
404 	rtmsg.rm_mh.rtm_version = RTM_VERSION;
405 	rtmsg.rm_mh.rtm_msglen	= sizeof (rtmsg);
406 	rtmsg.rm_mh.rtm_type	= type;
407 	rtmsg.rm_mh.rtm_pid	= getpid();
408 	rtmsg.rm_mh.rtm_flags	= RTF_GATEWAY | RTF_STATIC | flags;
409 	rtmsg.rm_mh.rtm_addrs	= RTA_GATEWAY | RTA_DST | RTA_NETMASK | RTA_IFP;
410 
411 	rtmsg.rm_gw.sin_family	= AF_INET;
412 	rtmsg.rm_gw.sin_addr	= *gateway_nbo;
413 
414 	rtmsg.rm_dst.sin_family = AF_INET;
415 	rtmsg.rm_dst.sin_addr.s_addr = htonl(INADDR_ANY);
416 
417 	rtmsg.rm_mask.sin_family = AF_INET;
418 	rtmsg.rm_mask.sin_addr.s_addr = htonl(0);
419 
420 	rtmsg.rm_ifp.sdl_family	= AF_LINK;
421 	rtmsg.rm_ifp.sdl_index	= if_nametoindex(ifname);
422 
423 	return (write(rtsock_fd, &rtmsg, sizeof (rtmsg)) == sizeof (rtmsg));
424 }
425 
426 /*
427  * add_default_route(): add the default route to the given gateway
428  *
429  *   input: const char *: the name of the interface associated with the route
430  *	    struct in_addr: the default gateway to add
431  *  output: int: 1 on success, 0 on failure
432  */
433 
434 int
435 add_default_route(const char *ifname, struct in_addr *gateway_nbo)
436 {
437 	if (strchr(ifname, ':') != NULL)	/* see README */
438 		return (1);
439 
440 	return (update_default_route(ifname, RTM_ADD, gateway_nbo, RTF_UP));
441 }
442 
443 /*
444  * del_default_route(): deletes the default route to the given gateway
445  *
446  *   input: const char *: the name of the interface associated with the route
447  *	    struct in_addr: if not INADDR_ANY, the default gateway to remove
448  *  output: int: 1 on success, 0 on failure
449  */
450 
451 int
452 del_default_route(const char *ifname, struct in_addr *gateway_nbo)
453 {
454 	if (strchr(ifname, ':') != NULL)
455 		return (1);
456 
457 	if (gateway_nbo->s_addr == htonl(INADDR_ANY)) /* no router */
458 		return (1);
459 
460 	return (update_default_route(ifname, RTM_DELETE, gateway_nbo, 0));
461 }
462 
463 /*
464  * inactivity_shutdown(): shuts down agent if there are no interfaces to manage
465  *
466  *   input: iu_tq_t *: unused
467  *	    void *: unused
468  *  output: void
469  */
470 
471 /* ARGSUSED */
472 void
473 inactivity_shutdown(iu_tq_t *tqp, void *arg)
474 {
475 	if (ifs_count() > 0)	/* shouldn't happen, but... */
476 		return;
477 
478 	iu_stop_handling_events(eh, DHCP_REASON_INACTIVITY, NULL, NULL);
479 }
480 
481 /*
482  * graceful_shutdown(): shuts down the agent gracefully
483  *
484  *   input: int: the signal that caused graceful_shutdown to be called
485  *  output: void
486  */
487 
488 void
489 graceful_shutdown(int sig)
490 {
491 	iu_stop_handling_events(eh, (sig == SIGTERM ? DHCP_REASON_TERMINATE :
492 	    DHCP_REASON_SIGNAL), drain_script, NULL);
493 }
494 
495 /*
496  * register_acknak(): registers dhcp_acknak() to be called back when ACK or
497  *		      NAK packets are received on a given interface
498  *
499  *   input: struct ifslist *: the interface to register for
500  *  output: int: 1 on success, 0 on failure
501  */
502 
503 int
504 register_acknak(struct ifslist *ifsp)
505 {
506 	iu_event_id_t	ack_id, ack_bcast_id = -1;
507 
508 	/*
509 	 * having an acknak id already registered isn't impossible;
510 	 * handle the situation as gracefully as possible.
511 	 */
512 
513 	if (ifsp->if_acknak_id != -1) {
514 		dhcpmsg(MSG_DEBUG, "register_acknak: acknak id pending, "
515 		    "attempting to cancel");
516 		if (unregister_acknak(ifsp) == 0)
517 			return (0);
518 	}
519 
520 	switch (ifsp->if_state) {
521 
522 	case BOUND:
523 	case REBINDING:
524 	case RENEWING:
525 
526 		ack_bcast_id = iu_register_event(eh, ifsp->if_sock_fd, POLLIN,
527 		    dhcp_acknak, ifsp);
528 
529 		if (ack_bcast_id == -1) {
530 			dhcpmsg(MSG_WARNING, "register_acknak: cannot "
531 			    "register to receive socket broadcasts");
532 			return (0);
533 		}
534 
535 		ack_id = iu_register_event(eh, ifsp->if_sock_ip_fd, POLLIN,
536 		    dhcp_acknak, ifsp);
537 		break;
538 
539 	default:
540 		ack_id = iu_register_event(eh, ifsp->if_dlpi_fd, POLLIN,
541 		    dhcp_acknak, ifsp);
542 		break;
543 	}
544 
545 	if (ack_id == -1) {
546 		dhcpmsg(MSG_WARNING, "register_acknak: cannot register event");
547 		(void) iu_unregister_event(eh, ack_bcast_id, NULL);
548 		return (0);
549 	}
550 
551 	ifsp->if_acknak_id = ack_id;
552 	hold_ifs(ifsp);
553 
554 	ifsp->if_acknak_bcast_id = ack_bcast_id;
555 	if (ifsp->if_acknak_bcast_id != -1) {
556 		hold_ifs(ifsp);
557 		dhcpmsg(MSG_DEBUG, "register_acknak: registered broadcast id "
558 		    "%d", ack_bcast_id);
559 	}
560 
561 	dhcpmsg(MSG_DEBUG, "register_acknak: registered acknak id %d", ack_id);
562 	return (1);
563 }
564 
565 /*
566  * unregister_acknak(): unregisters dhcp_acknak() to be called back
567  *
568  *   input: struct ifslist *: the interface to unregister for
569  *  output: int: 1 on success, 0 on failure
570  */
571 
572 int
573 unregister_acknak(struct ifslist *ifsp)
574 {
575 	if (ifsp->if_acknak_id != -1) {
576 
577 		if (iu_unregister_event(eh, ifsp->if_acknak_id, NULL) == 0) {
578 			dhcpmsg(MSG_DEBUG, "unregister_acknak: cannot "
579 			    "unregister acknak id %d on %s",
580 			    ifsp->if_acknak_id, ifsp->if_name);
581 			return (0);
582 		}
583 
584 		dhcpmsg(MSG_DEBUG, "unregister_acknak: unregistered acknak id "
585 		    "%d", ifsp->if_acknak_id);
586 
587 		ifsp->if_acknak_id = -1;
588 		(void) release_ifs(ifsp);
589 	}
590 
591 	if (ifsp->if_acknak_bcast_id != -1) {
592 
593 		if (iu_unregister_event(eh, ifsp->if_acknak_bcast_id, NULL)
594 		    == 0) {
595 			dhcpmsg(MSG_DEBUG, "unregister_acknak: cannot "
596 			    "unregister broadcast id %d on %s",
597 			    ifsp->if_acknak_id, ifsp->if_name);
598 			return (0);
599 		}
600 
601 		dhcpmsg(MSG_DEBUG, "unregister_acknak: unregistered "
602 		    "broadcast id %d", ifsp->if_acknak_bcast_id);
603 
604 		ifsp->if_acknak_bcast_id = -1;
605 		(void) release_ifs(ifsp);
606 	}
607 
608 	return (1);
609 }
610 
611 /*
612  * bind_sock(): binds a socket to a given IP address and port number
613  *
614  *   input: int: the socket to bind
615  *	    in_port_t: the port number to bind to, host byte order
616  *	    in_addr_t: the address to bind to, host byte order
617  *  output: int: 1 on success, 0 on failure
618  */
619 
620 int
621 bind_sock(int fd, in_port_t port_hbo, in_addr_t addr_hbo)
622 {
623 	struct sockaddr_in	sin;
624 	int			on = 1;
625 
626 	(void) memset(&sin, 0, sizeof (struct sockaddr_in));
627 	sin.sin_family = AF_INET;
628 	sin.sin_port   = htons(port_hbo);
629 	sin.sin_addr.s_addr = htonl(addr_hbo);
630 
631 	(void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int));
632 
633 	return (bind(fd, (struct sockaddr *)&sin, sizeof (sin)) == 0);
634 }
635 
636 /*
637  * valid_hostname(): check whether a string is a valid hostname
638  *
639  *   input: const char *: the string to verify as a hostname
640  *  output: boolean_t: B_TRUE if the string is a valid hostname
641  *
642  * Note that we accept both host names beginning with a digit and
643  * those containing hyphens.  Neither is strictly legal according
644  * to the RFCs, but both are in common practice, so we endeavour
645  * to not break what customers are using.
646  */
647 
648 static boolean_t
649 valid_hostname(const char *hostname)
650 {
651 	unsigned int i;
652 
653 	for (i = 0; hostname[i] != '\0'; i++) {
654 
655 		if (isalpha(hostname[i]) || isdigit(hostname[i]) ||
656 		    (((hostname[i] == '-') || (hostname[i] == '.')) && (i > 0)))
657 			continue;
658 
659 		return (B_FALSE);
660 	}
661 
662 	return (i > 0);
663 }
664 
665 /*
666  * iffile_to_hostname(): return the hostname contained on a line of the form
667  *
668  * [ ^I]*inet[ ^I]+hostname[\n]*\0
669  *
670  * in the file located at the specified path
671  *
672  *   input: const char *: the path of the file to look in for the hostname
673  *  output: const char *: the hostname at that path, or NULL on failure
674  */
675 
676 #define	IFLINE_MAX	1024	/* maximum length of a hostname.<if> line */
677 
678 const char *
679 iffile_to_hostname(const char *path)
680 {
681 	FILE		*fp;
682 	static char	ifline[IFLINE_MAX];
683 
684 	fp = fopen(path, "r");
685 	if (fp == NULL)
686 		return (NULL);
687 
688 	/*
689 	 * /etc/hostname.<if> may contain multiple ifconfig commands, but each
690 	 * such command is on a separate line (see the "while read ifcmds" code
691 	 * in /etc/init.d/inetinit).  Thus we will read the file a line at a
692 	 * time, searching for a line of the form
693 	 *
694 	 * [ ^I]*inet[ ^I]+hostname[\n]*\0
695 	 *
696 	 * extract the host name from it, and check it for validity.
697 	 */
698 	while (fgets(ifline, sizeof (ifline), fp) != NULL) {
699 		char *p;
700 
701 		if ((p = strstr(ifline, "inet")) != NULL) {
702 			if ((p != ifline) && !isspace(p[-1])) {
703 				(void) fclose(fp);
704 				return (NULL);
705 			}
706 			p += 4;	/* skip over "inet" and expect spaces or tabs */
707 			if ((*p == '\n') || (*p == '\0')) {
708 				(void) fclose(fp);
709 				return (NULL);
710 			}
711 			if (isspace(*p)) {
712 				char *nlptr;
713 
714 				/* no need to read more of the file */
715 				(void) fclose(fp);
716 
717 				while (isspace(*p))
718 					p++;
719 				if ((nlptr = strrchr(p, '\n')) != NULL)
720 					*nlptr = '\0';
721 				if (strlen(p) > MAXHOSTNAMELEN) {
722 					dhcpmsg(MSG_WARNING,
723 					    "iffile_to_hostname:"
724 					    " host name too long");
725 					return (NULL);
726 				}
727 				if (valid_hostname(p)) {
728 					return (p);
729 				} else {
730 					dhcpmsg(MSG_WARNING,
731 					    "iffile_to_hostname:"
732 					    " host name not valid");
733 					return (NULL);
734 				}
735 			} else {
736 				(void) fclose(fp);
737 				return (NULL);
738 			}
739 		}
740 	}
741 
742 	(void) fclose(fp);
743 	return (NULL);
744 }
745