xref: /freebsd/libexec/bootpd/bootpd.c (revision 2f513db7)
1 /************************************************************************
2           Copyright 1988, 1991 by Carnegie Mellon University
3 
4                           All Rights Reserved
5 
6 Permission to use, copy, modify, and distribute this software and its
7 documentation for any purpose and without fee is hereby granted, provided
8 that the above copyright notice appear in all copies and that both that
9 copyright notice and this permission notice appear in supporting
10 documentation, and that the name of Carnegie Mellon University not be used
11 in advertising or publicity pertaining to distribution of the software
12 without specific, written prior permission.
13 
14 CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
15 SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
16 IN NO EVENT SHALL CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
17 DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
18 PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
19 ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
20 SOFTWARE.
21 
22 ************************************************************************/
23 
24 /*
25  * BOOTP (bootstrap protocol) server daemon.
26  *
27  * Answers BOOTP request packets from booting client machines.
28  * See [SRI-NIC]<RFC>RFC951.TXT for a description of the protocol.
29  * See [SRI-NIC]<RFC>RFC1048.TXT for vendor-information extensions.
30  * See RFC 1395 for option tags 14-17.
31  * See accompanying man page -- bootpd.8
32  *
33  * HISTORY
34  *	See ./Changes
35  *
36  * BUGS
37  *	See ./ToDo
38  */
39 
40 #include <sys/cdefs.h>
41 __FBSDID("$FreeBSD$");
42 
43 #include <sys/types.h>
44 #include <sys/param.h>
45 #include <sys/socket.h>
46 #include <sys/ioctl.h>
47 #include <sys/file.h>
48 #include <sys/time.h>
49 #include <sys/stat.h>
50 #include <sys/utsname.h>
51 
52 #include <net/if.h>
53 #include <netinet/in.h>
54 #include <arpa/inet.h>	/* inet_ntoa */
55 
56 #ifndef	NO_UNISTD
57 #include <unistd.h>
58 #endif
59 
60 #include <stdlib.h>
61 #include <signal.h>
62 #include <stdio.h>
63 #include <string.h>
64 #include <errno.h>
65 #include <ctype.h>
66 #include <netdb.h>
67 #include <paths.h>
68 #include <syslog.h>
69 #include <assert.h>
70 #include <inttypes.h>
71 
72 #ifdef	NO_SETSID
73 # include <fcntl.h>		/* for O_RDONLY, etc */
74 #endif
75 
76 #ifndef	USE_BFUNCS
77 # include <memory.h>
78 /* Yes, memcpy is OK here (no overlapped copies). */
79 # define bcopy(a,b,c)    memcpy(b,a,c)
80 # define bzero(p,l)      memset(p,0,l)
81 # define bcmp(a,b,c)     memcmp(a,b,c)
82 #endif
83 
84 #include "bootp.h"
85 #include "hash.h"
86 #include "hwaddr.h"
87 #include "bootpd.h"
88 #include "dovend.h"
89 #include "getif.h"
90 #include "readfile.h"
91 #include "report.h"
92 #include "tzone.h"
93 #include "patchlevel.h"
94 
95 #ifndef CONFIG_FILE
96 #define CONFIG_FILE		"/etc/bootptab"
97 #endif
98 #ifndef DUMPTAB_FILE
99 #define DUMPTAB_FILE		"/tmp/bootpd.dump"
100 #endif
101 
102 
103 
104 /*
105  * Externals, forward declarations, and global variables
106  */
107 
108 extern void dumptab(char *);
109 
110 PRIVATE void catcher(int);
111 PRIVATE int chk_access(char *, int32 *);
112 #ifdef VEND_CMU
113 PRIVATE void dovend_cmu(struct bootp *, struct host *);
114 #endif
115 PRIVATE void dovend_rfc1048(struct bootp *, struct host *, int32);
116 PRIVATE void handle_reply(void);
117 PRIVATE void handle_request(void);
118 PRIVATE void sendreply(int forward, int32 dest_override);
119 PRIVATE void usage(void);
120 
121 /*
122  * IP port numbers for client and server obtained from /etc/services
123  */
124 
125 u_short bootps_port, bootpc_port;
126 
127 
128 /*
129  * Internet socket and interface config structures
130  */
131 
132 struct sockaddr_in bind_addr;	/* Listening */
133 struct sockaddr_in recv_addr;	/* Packet source */
134 struct sockaddr_in send_addr;	/*  destination */
135 
136 
137 /*
138  * option defaults
139  */
140 int debug = 0;					/* Debugging flag (level) */
141 struct timeval actualtimeout =
142 {								/* fifteen minutes */
143 	15 * 60L,					/* tv_sec */
144 	0							/* tv_usec */
145 };
146 int arpmod = TRUE;				/* modify the ARP table */
147 
148 /*
149  * General
150  */
151 
152 int s;							/* Socket file descriptor */
153 char *pktbuf;					/* Receive packet buffer */
154 int pktlen;
155 char *progname;
156 char *chdir_path;
157 struct in_addr my_ip_addr;
158 
159 static const char *hostname;
160 static char default_hostname[MAXHOSTNAMELEN];
161 
162 /* Flags set by signal catcher. */
163 PRIVATE int do_readtab = 0;
164 PRIVATE int do_dumptab = 0;
165 
166 /*
167  * Globals below are associated with the bootp database file (bootptab).
168  */
169 
170 char *bootptab = CONFIG_FILE;
171 char *bootpd_dump = DUMPTAB_FILE;
172 
173 
174 
175 /*
176  * Initialization such as command-line processing is done and then the
177  * main server loop is started.
178  */
179 
180 int
181 main(argc, argv)
182 	int argc;
183 	char **argv;
184 {
185 	struct timeval *timeout;
186 	struct bootp *bp;
187 	struct servent *servp;
188 	struct hostent *hep;
189 	char *stmp;
190 	socklen_t ba_len, ra_len;
191 	int n;
192 	int nfound;
193 	fd_set readfds;
194 	int standalone;
195 #ifdef	SA_NOCLDSTOP	/* Have POSIX sigaction(2). */
196 	struct sigaction sa;
197 #endif
198 
199 	progname = strrchr(argv[0], '/');
200 	if (progname) progname++;
201 	else progname = argv[0];
202 
203 	/*
204 	 * Initialize logging.
205 	 */
206 	report_init(0);				/* uses progname */
207 
208 	/*
209 	 * Log startup
210 	 */
211 	report(LOG_INFO, "version %s.%d", VERSION, PATCHLEVEL);
212 
213 	/* Debugging for compilers with struct padding. */
214 	assert(sizeof(struct bootp) == BP_MINPKTSZ);
215 
216 	/* Get space for receiving packets and composing replies. */
217 	pktbuf = malloc(MAX_MSG_SIZE);
218 	if (!pktbuf) {
219 		report(LOG_ERR, "malloc failed");
220 		exit(1);
221 	}
222 	bp = (struct bootp *) pktbuf;
223 
224 	/*
225 	 * Check to see if a socket was passed to us from inetd.
226 	 *
227 	 * Use getsockname() to determine if descriptor 0 is indeed a socket
228 	 * (and thus we are probably a child of inetd) or if it is instead
229 	 * something else and we are running standalone.
230 	 */
231 	s = 0;
232 	ba_len = sizeof(bind_addr);
233 	bzero((char *) &bind_addr, ba_len);
234 	errno = 0;
235 	standalone = TRUE;
236 	if (getsockname(s, (struct sockaddr *) &bind_addr, &ba_len) == 0) {
237 		/*
238 		 * Descriptor 0 is a socket.  Assume we are a child of inetd.
239 		 */
240 		if (bind_addr.sin_family == AF_INET) {
241 			standalone = FALSE;
242 			bootps_port = ntohs(bind_addr.sin_port);
243 		} else {
244 			/* Some other type of socket? */
245 			report(LOG_ERR, "getsockname: not an INET socket");
246 		}
247 	}
248 
249 	/*
250 	 * Set defaults that might be changed by option switches.
251 	 */
252 	stmp = NULL;
253 	timeout = &actualtimeout;
254 
255 	if (gethostname(default_hostname, sizeof(default_hostname) - 1) < 0) {
256 		report(LOG_ERR, "bootpd: can't get hostname\n");
257 		exit(1);
258 	}
259 	default_hostname[sizeof(default_hostname) - 1] = '\0';
260 	hostname = default_hostname;
261 
262 	/*
263 	 * Read switches.
264 	 */
265 	for (argc--, argv++; argc > 0; argc--, argv++) {
266 		if (argv[0][0] != '-')
267 			break;
268 		switch (argv[0][1]) {
269 
270 		case 'a':				/* don't modify the ARP table */
271 			arpmod = FALSE;
272 			break;
273 		case 'c':				/* chdir_path */
274 			if (argv[0][2]) {
275 				stmp = &(argv[0][2]);
276 			} else {
277 				argc--;
278 				argv++;
279 				stmp = argv[0];
280 			}
281 			if (!stmp || (stmp[0] != '/')) {
282 				report(LOG_ERR,
283 						"bootpd: invalid chdir specification\n");
284 				break;
285 			}
286 			chdir_path = stmp;
287 			break;
288 
289 		case 'd':				/* debug level */
290 			if (argv[0][2]) {
291 				stmp = &(argv[0][2]);
292 			} else if (argv[1] && argv[1][0] == '-') {
293 				/*
294 				 * Backwards-compatible behavior:
295 				 * no parameter, so just increment the debug flag.
296 				 */
297 				debug++;
298 				break;
299 			} else {
300 				argc--;
301 				argv++;
302 				stmp = argv[0];
303 			}
304 			if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
305 				report(LOG_ERR,
306 						"%s: invalid debug level\n", progname);
307 				break;
308 			}
309 			debug = n;
310 			break;
311 
312 		case 'h':				/* override hostname */
313 			if (argv[0][2]) {
314 				stmp = &(argv[0][2]);
315 			} else {
316 				argc--;
317 				argv++;
318 				stmp = argv[0];
319 			}
320 			if (!stmp) {
321 				report(LOG_ERR,
322 						"bootpd: missing hostname\n");
323 				break;
324 			}
325 			hostname = stmp;
326 			break;
327 
328 		case 'i':				/* inetd mode */
329 			standalone = FALSE;
330 			break;
331 
332 		case 's':				/* standalone mode */
333 			standalone = TRUE;
334 			break;
335 
336 		case 't':				/* timeout */
337 			if (argv[0][2]) {
338 				stmp = &(argv[0][2]);
339 			} else {
340 				argc--;
341 				argv++;
342 				stmp = argv[0];
343 			}
344 			if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
345 				report(LOG_ERR,
346 						"%s: invalid timeout specification\n", progname);
347 				break;
348 			}
349 			actualtimeout.tv_sec = (int32) (60 * n);
350 			/*
351 			 * If the actual timeout is zero, pass a NULL pointer
352 			 * to select so it blocks indefinitely, otherwise,
353 			 * point to the actual timeout value.
354 			 */
355 			timeout = (n > 0) ? &actualtimeout : NULL;
356 			break;
357 
358 		default:
359 			report(LOG_ERR, "%s: unknown switch: -%c\n",
360 					progname, argv[0][1]);
361 			usage();
362 			break;
363 
364 		} /* switch */
365 	} /* for args */
366 
367 	/*
368 	 * Override default file names if specified on the command line.
369 	 */
370 	if (argc > 0)
371 		bootptab = argv[0];
372 
373 	if (argc > 1)
374 		bootpd_dump = argv[1];
375 
376 	/*
377 	 * Get my hostname and IP address.
378 	 */
379 
380 	hep = gethostbyname(hostname);
381 	if (!hep) {
382 		report(LOG_ERR, "Can not get my IP address\n");
383 		exit(1);
384 	}
385 	bcopy(hep->h_addr, (char *)&my_ip_addr, sizeof(my_ip_addr));
386 
387 	if (standalone) {
388 		/*
389 		 * Go into background and disassociate from controlling terminal.
390 		 */
391 		if (debug < 3) {
392 			if (fork())
393 				exit(0);
394 #ifdef	NO_SETSID
395 			setpgrp(0,0);
396 #ifdef TIOCNOTTY
397 			n = open(_PATH_TTY, O_RDWR);
398 			if (n >= 0) {
399 				ioctl(n, TIOCNOTTY, (char *) 0);
400 				(void) close(n);
401 			}
402 #endif	/* TIOCNOTTY */
403 #else	/* SETSID */
404 			if (setsid() < 0)
405 				perror("setsid");
406 #endif	/* SETSID */
407 		} /* if debug < 3 */
408 
409 		/*
410 		 * Nuke any timeout value
411 		 */
412 		timeout = NULL;
413 
414 	} /* if standalone (1st) */
415 
416 	/* Set the cwd (i.e. to /tftpboot) */
417 	if (chdir_path) {
418 		if (chdir(chdir_path) < 0)
419 			report(LOG_ERR, "%s: chdir failed", chdir_path);
420 	}
421 
422 	/* Get the timezone. */
423 	tzone_init();
424 
425 	/* Allocate hash tables. */
426 	rdtab_init();
427 
428 	/*
429 	 * Read the bootptab file.
430 	 */
431 	readtab(1);					/* force read */
432 
433 	if (standalone) {
434 
435 		/*
436 		 * Create a socket.
437 		 */
438 		if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
439 			report(LOG_ERR, "socket: %s", get_network_errmsg());
440 			exit(1);
441 		}
442 
443 		/*
444 		 * Get server's listening port number
445 		 */
446 		servp = getservbyname("bootps", "udp");
447 		if (servp) {
448 			bootps_port = ntohs((u_short) servp->s_port);
449 		} else {
450 			bootps_port = (u_short) IPPORT_BOOTPS;
451 			report(LOG_ERR,
452 				"bootps/udp: unknown service -- using port %d",
453 				   bootps_port);
454 		}
455 
456 		/*
457 		 * Bind socket to BOOTPS port.
458 		 */
459 		bind_addr.sin_family = AF_INET;
460 		bind_addr.sin_addr.s_addr = INADDR_ANY;
461 		bind_addr.sin_port = htons(bootps_port);
462 		if (bind(s, (struct sockaddr *) &bind_addr,
463 				 sizeof(bind_addr)) < 0)
464 		{
465 			report(LOG_ERR, "bind: %s", get_network_errmsg());
466 			exit(1);
467 		}
468 	} /* if standalone (2nd)*/
469 
470 	/*
471 	 * Get destination port number so we can reply to client
472 	 */
473 	servp = getservbyname("bootpc", "udp");
474 	if (servp) {
475 		bootpc_port = ntohs(servp->s_port);
476 	} else {
477 		report(LOG_ERR,
478 			   "bootpc/udp: unknown service -- using port %d",
479 			   IPPORT_BOOTPC);
480 		bootpc_port = (u_short) IPPORT_BOOTPC;
481 	}
482 
483 	/*
484 	 * Set up signals to read or dump the table.
485 	 */
486 #ifdef	SA_NOCLDSTOP	/* Have POSIX sigaction(2). */
487 	sa.sa_handler = catcher;
488 	sigemptyset(&sa.sa_mask);
489 	sa.sa_flags = 0;
490 	if (sigaction(SIGHUP, &sa, NULL) < 0) {
491 		report(LOG_ERR, "sigaction: %s", get_errmsg());
492 		exit(1);
493 	}
494 	if (sigaction(SIGUSR1, &sa, NULL) < 0) {
495 		report(LOG_ERR, "sigaction: %s", get_errmsg());
496 		exit(1);
497 	}
498 #else	/* SA_NOCLDSTOP */
499 	/* Old-fashioned UNIX signals */
500 	if ((int) signal(SIGHUP, catcher) < 0) {
501 		report(LOG_ERR, "signal: %s", get_errmsg());
502 		exit(1);
503 	}
504 	if ((int) signal(SIGUSR1, catcher) < 0) {
505 		report(LOG_ERR, "signal: %s", get_errmsg());
506 		exit(1);
507 	}
508 #endif	/* SA_NOCLDSTOP */
509 
510 	/*
511 	 * Process incoming requests.
512 	 */
513 	FD_ZERO(&readfds);
514 	for (;;) {
515 		struct timeval tv;
516 
517 		FD_SET(s, &readfds);
518 		if (timeout)
519 			tv = *timeout;
520 
521 		nfound = select(s + 1, &readfds, NULL, NULL,
522 						(timeout) ? &tv : NULL);
523 		if (nfound < 0) {
524 			if (errno != EINTR) {
525 				report(LOG_ERR, "select: %s", get_errmsg());
526 			}
527 			/*
528 			 * Call readtab() or dumptab() here to avoid the
529 			 * dangers of doing I/O from a signal handler.
530 			 */
531 			if (do_readtab) {
532 				do_readtab = 0;
533 				readtab(1);		/* force read */
534 			}
535 			if (do_dumptab) {
536 				do_dumptab = 0;
537 				dumptab(bootpd_dump);
538 			}
539 			continue;
540 		}
541 		if (!FD_ISSET(s, &readfds)) {
542 			if (debug > 1)
543 				report(LOG_INFO, "exiting after %jd minutes of inactivity",
544 					   (intmax_t)actualtimeout.tv_sec / 60);
545 			exit(0);
546 		}
547 		ra_len = sizeof(recv_addr);
548 		n = recvfrom(s, pktbuf, MAX_MSG_SIZE, 0,
549 					 (struct sockaddr *) &recv_addr, &ra_len);
550 		if (n <= 0) {
551 			continue;
552 		}
553 		if (debug > 1) {
554 			report(LOG_INFO, "recvd pkt from IP addr %s",
555 				   inet_ntoa(recv_addr.sin_addr));
556 		}
557 		if (n < sizeof(struct bootp)) {
558 			if (debug) {
559 				report(LOG_NOTICE, "received short packet");
560 			}
561 			continue;
562 		}
563 		pktlen = n;
564 
565 		readtab(0);				/* maybe re-read bootptab */
566 
567 		switch (bp->bp_op) {
568 		case BOOTREQUEST:
569 			handle_request();
570 			break;
571 		case BOOTREPLY:
572 			handle_reply();
573 			break;
574 		}
575 	}
576 	return 0;
577 }
578 
579 
580 
581 
582 /*
583  * Print "usage" message and exit
584  */
585 
586 PRIVATE void
587 usage()
588 {
589 	fprintf(stderr,
590 		"usage: bootpd [-a] [-i | -s] [-c chdir-path] [-d level] [-h hostname]\n"
591 		"              [-t timeout] [bootptab [dumpfile]]\n");
592 	fprintf(stderr, "       -a\tdon't modify ARP table\n");
593 	fprintf(stderr, "       -c n\tset current directory\n");
594 	fprintf(stderr, "       -d n\tset debug level\n");
595 	fprintf(stderr, "       -h n\tset the hostname to listen on\n");
596 	fprintf(stderr, "       -i\tforce inetd mode (run as child of inetd)\n");
597 	fprintf(stderr, "       -s\tforce standalone mode (run without inetd)\n");
598 	fprintf(stderr, "       -t n\tset inetd exit timeout to n minutes\n");
599 	exit(1);
600 }
601 
602 /* Signal catchers */
603 PRIVATE void
604 catcher(sig)
605 	int sig;
606 {
607 	if (sig == SIGHUP)
608 		do_readtab = 1;
609 	if (sig == SIGUSR1)
610 		do_dumptab = 1;
611 #if	!defined(SA_NOCLDSTOP) && defined(SYSV)
612 	/* For older "System V" derivatives with no sigaction(). */
613 	signal(sig, catcher);
614 #endif
615 }
616 
617 
618 
619 /*
620  * Process BOOTREQUEST packet.
621  *
622  * Note:  This version of the bootpd.c server never forwards
623  * a request to another server.  That is the job of a gateway
624  * program such as the "bootpgw" program included here.
625  *
626  * (Also this version does not interpret the hostname field of
627  * the request packet;  it COULD do a name->address lookup and
628  * forward the request there.)
629  */
630 PRIVATE void
631 handle_request()
632 {
633 	struct bootp *bp = (struct bootp *) pktbuf;
634 	struct host *hp = NULL;
635 	struct host dummyhost;
636 	int32 bootsize = 0;
637 	unsigned hlen, hashcode;
638 	int32 dest;
639 	char realpath[1024];
640 	char *clntpath;
641 	char *homedir, *bootfile;
642 	int n;
643 
644 	if (bp->bp_htype >= hwinfocnt) {
645 		report(LOG_NOTICE, "bad hw addr type %u", bp->bp_htype);
646 		return;
647 	}
648 	bp->bp_file[sizeof(bp->bp_file)-1] = '\0';
649 
650 	/* XXX - SLIP init: Set bp_ciaddr = recv_addr here? */
651 
652 	/*
653 	 * If the servername field is set, compare it against us.
654 	 * If we're not being addressed, ignore this request.
655 	 * If the server name field is null, throw in our name.
656 	 */
657 	if (strlen(bp->bp_sname)) {
658 		if (strcmp(bp->bp_sname, hostname)) {
659 			if (debug)
660 				report(LOG_INFO, "\
661 ignoring request for server %s from client at %s address %s",
662 					   bp->bp_sname, netname(bp->bp_htype),
663 					   haddrtoa(bp->bp_chaddr, bp->bp_hlen));
664 			/* XXX - Is it correct to ignore such a request? -gwr */
665 			return;
666 		}
667 	} else {
668 		strcpy(bp->bp_sname, hostname);
669 	}
670 
671 	/* Convert the request into a reply. */
672 	bp->bp_op = BOOTREPLY;
673 	if (bp->bp_ciaddr.s_addr == 0) {
674 		/*
675 		 * client doesn't know his IP address,
676 		 * search by hardware address.
677 		 */
678 		if (debug > 1) {
679 			report(LOG_INFO, "request from %s address %s",
680 				   netname(bp->bp_htype),
681 				   haddrtoa(bp->bp_chaddr, bp->bp_hlen));
682 		}
683 		hlen = haddrlength(bp->bp_htype);
684 		if (hlen != bp->bp_hlen) {
685 			report(LOG_NOTICE, "bad addr len from %s address %s",
686 				   netname(bp->bp_htype),
687 				   haddrtoa(bp->bp_chaddr, hlen));
688 		}
689 		dummyhost.htype = bp->bp_htype;
690 		bcopy(bp->bp_chaddr, dummyhost.haddr, hlen);
691 		hashcode = hash_HashFunction(bp->bp_chaddr, hlen);
692 		hp = (struct host *) hash_Lookup(hwhashtable, hashcode, hwlookcmp,
693 										 &dummyhost);
694 		if (hp == NULL &&
695 			bp->bp_htype == HTYPE_IEEE802)
696 		{
697 			/* Try again with address in "canonical" form. */
698 			haddr_conv802(bp->bp_chaddr, dummyhost.haddr, hlen);
699 			if (debug > 1) {
700 				report(LOG_INFO, "\
701 HW addr type is IEEE 802.  convert to %s and check again\n",
702 					   haddrtoa(dummyhost.haddr, bp->bp_hlen));
703 			}
704 			hashcode = hash_HashFunction(dummyhost.haddr, hlen);
705 			hp = (struct host *) hash_Lookup(hwhashtable, hashcode,
706 											 hwlookcmp, &dummyhost);
707 		}
708 		if (hp == NULL) {
709 			/*
710 			 * XXX - Add dynamic IP address assignment?
711 			 */
712 			if (debug)
713 				report(LOG_NOTICE, "unknown client %s address %s",
714 					   netname(bp->bp_htype),
715 					   haddrtoa(bp->bp_chaddr, bp->bp_hlen));
716 			return; /* not found */
717 		}
718 		(bp->bp_yiaddr).s_addr = hp->iaddr.s_addr;
719 
720 	} else {
721 
722 		/*
723 		 * search by IP address.
724 		 */
725 		if (debug > 1) {
726 			report(LOG_INFO, "request from IP addr %s",
727 				   inet_ntoa(bp->bp_ciaddr));
728 		}
729 		dummyhost.iaddr.s_addr = bp->bp_ciaddr.s_addr;
730 		hashcode = hash_HashFunction((u_char *) &(bp->bp_ciaddr.s_addr), 4);
731 		hp = (struct host *) hash_Lookup(iphashtable, hashcode, iplookcmp,
732 										 &dummyhost);
733 		if (hp == NULL) {
734 			if (debug) {
735 				report(LOG_NOTICE, "IP address not found: %s",
736 					   inet_ntoa(bp->bp_ciaddr));
737 			}
738 			return;
739 		}
740 	}
741 
742 	if (debug) {
743 		report(LOG_INFO, "found %s (%s)", inet_ntoa(hp->iaddr),
744 			   hp->hostname->string);
745 	}
746 
747 	/*
748 	 * If there is a response delay threshold, ignore requests
749 	 * with a timestamp lower than the threshold.
750 	 */
751 	if (hp->flags.min_wait) {
752 		u_int32 t = (u_int32) ntohs(bp->bp_secs);
753 		if (t < hp->min_wait) {
754 			if (debug > 1)
755 				report(LOG_INFO,
756 					   "ignoring request due to timestamp (%d < %d)",
757 					   t, hp->min_wait);
758 			return;
759 		}
760 	}
761 
762 #ifdef	YORK_EX_OPTION
763 	/*
764 	 * The need for the "ex" tag arose out of the need to empty
765 	 * shared networked drives on diskless PCs.  This solution is
766 	 * not very clean but it does work fairly well.
767 	 * Written by Edmund J. Sutcliffe <edmund@york.ac.uk>
768 	 *
769 	 * XXX - This could compromise security if a non-trusted user
770 	 * managed to write an entry in the bootptab with :ex=trojan:
771 	 * so I would leave this turned off unless you need it. -gwr
772 	 */
773 	/* Run a program, passing the client name as a parameter. */
774 	if (hp->flags.exec_file) {
775 		char tst[100];
776 		/* XXX - Check string lengths? -gwr */
777 		strcpy (tst, hp->exec_file->string);
778 		strcat (tst, " ");
779 		strcat (tst, hp->hostname->string);
780 		strcat (tst, " &");
781 		if (debug)
782 			report(LOG_INFO, "executing %s", tst);
783 		system(tst);	/* Hope this finishes soon... */
784 	}
785 #endif	/* YORK_EX_OPTION */
786 
787 	/*
788 	 * If a specific TFTP server address was specified in the bootptab file,
789 	 * fill it in, otherwise zero it.
790 	 * XXX - Rather than zero it, should it be the bootpd address? -gwr
791 	 */
792 	(bp->bp_siaddr).s_addr = (hp->flags.bootserver) ?
793 		hp->bootserver.s_addr : 0L;
794 
795 #ifdef	STANFORD_PROM_COMPAT
796 	/*
797 	 * Stanford bootp PROMs (for a Sun?) have no way to leave
798 	 * the boot file name field blank (because the boot file
799 	 * name is automatically generated from some index).
800 	 * As a work-around, this little hack allows those PROMs to
801 	 * specify "sunboot14" with the same effect as a NULL name.
802 	 * (The user specifies boot device 14 or some such magic.)
803 	 */
804 	if (strcmp(bp->bp_file, "sunboot14") == 0)
805 		bp->bp_file[0] = '\0';	/* treat it as unspecified */
806 #endif
807 
808 	/*
809 	 * Fill in the client's proper bootfile.
810 	 *
811 	 * If the client specifies an absolute path, try that file with a
812 	 * ".host" suffix and then without.  If the file cannot be found, no
813 	 * reply is made at all.
814 	 *
815 	 * If the client specifies a null or relative file, use the following
816 	 * table to determine the appropriate action:
817 	 *
818 	 *  Homedir      Bootfile    Client's file
819 	 * specified?   specified?   specification   Action
820 	 * -------------------------------------------------------------------
821 	 *      No          No          Null         Send null filename
822 	 *      No          No          Relative     Discard request
823 	 *      No          Yes         Null         Send if absolute else null
824 	 *      No          Yes         Relative     Discard request     *XXX
825 	 *      Yes         No          Null         Send null filename
826 	 *      Yes         No          Relative     Lookup with ".host"
827 	 *      Yes         Yes         Null         Send home/boot or bootfile
828 	 *      Yes         Yes         Relative     Lookup with ".host" *XXX
829 	 *
830 	 */
831 
832 	/*
833 	 * XXX - I don't like the policy of ignoring a client when the
834 	 * boot file is not accessible.  The TFTP server might not be
835 	 * running on the same machine as the BOOTP server, in which
836 	 * case checking accessibility of the boot file is pointless.
837 	 *
838 	 * Therefore, file accessibility is now demanded ONLY if you
839 	 * define CHECK_FILE_ACCESS in the Makefile options. -gwr
840 	 */
841 
842 	/*
843 	 * The "real" path is as seen by the BOOTP daemon on this
844 	 * machine, while the client path is relative to the TFTP
845 	 * daemon chroot directory (i.e. /tftpboot).
846 	 */
847 	if (hp->flags.tftpdir) {
848 		snprintf(realpath, sizeof(realpath), "%s", hp->tftpdir->string);
849 		clntpath = &realpath[strlen(realpath)];
850 	} else {
851 		realpath[0] = '\0';
852 		clntpath = realpath;
853 	}
854 
855 	/*
856 	 * Determine client's requested homedir and bootfile.
857 	 */
858 	homedir = NULL;
859 	bootfile = NULL;
860 	if (bp->bp_file[0]) {
861 		homedir = bp->bp_file;
862 		bootfile = strrchr(homedir, '/');
863 		if (bootfile) {
864 			if (homedir == bootfile)
865 				homedir = NULL;
866 			*bootfile++ = '\0';
867 		} else {
868 			/* no "/" in the string */
869 			bootfile = homedir;
870 			homedir = NULL;
871 		}
872 		if (debug > 2) {
873 			report(LOG_INFO, "requested path=\"%s\"  file=\"%s\"",
874 				   (homedir) ? homedir : "",
875 				   (bootfile) ? bootfile : "");
876 		}
877 	}
878 
879 	/*
880 	 * Specifications in bootptab override client requested values.
881 	 */
882 	if (hp->flags.homedir)
883 		homedir = hp->homedir->string;
884 	if (hp->flags.bootfile)
885 		bootfile = hp->bootfile->string;
886 
887 	/*
888 	 * Construct bootfile path.
889 	 */
890 	if (homedir) {
891 		if (homedir[0] != '/')
892 			strcat(clntpath, "/");
893 		strcat(clntpath, homedir);
894 		homedir = NULL;
895 	}
896 	if (bootfile) {
897 		if (bootfile[0] != '/')
898 			strcat(clntpath, "/");
899 		strcat(clntpath, bootfile);
900 		bootfile = NULL;
901 	}
902 
903 	/*
904 	 * First try to find the file with a ".host" suffix
905 	 */
906 	n = strlen(clntpath);
907 	strcat(clntpath, ".");
908 	strcat(clntpath, hp->hostname->string);
909 	if (chk_access(realpath, &bootsize) < 0) {
910 		clntpath[n] = 0;			/* Try it without the suffix */
911 		if (chk_access(realpath, &bootsize) < 0) {
912 			/* neither "file.host" nor "file" was found */
913 #ifdef	CHECK_FILE_ACCESS
914 
915 			if (bp->bp_file[0]) {
916 				/*
917 				 * Client wanted specific file
918 				 * and we didn't have it.
919 				 */
920 				report(LOG_NOTICE,
921 					   "requested file not found: \"%s\"", clntpath);
922 				return;
923 			}
924 			/*
925 			 * Client didn't ask for a specific file and we couldn't
926 			 * access the default file, so just zero-out the bootfile
927 			 * field in the packet and continue processing the reply.
928 			 */
929 			bzero(bp->bp_file, sizeof(bp->bp_file));
930 			goto null_file_name;
931 
932 #else	/* CHECK_FILE_ACCESS */
933 
934 			/* Complain only if boot file size was needed. */
935 			if (hp->flags.bootsize_auto) {
936 				report(LOG_ERR, "can not determine size of file \"%s\"",
937 					   clntpath);
938 			}
939 
940 #endif	/* CHECK_FILE_ACCESS */
941 		}
942 	}
943 	strncpy(bp->bp_file, clntpath, BP_FILE_LEN);
944 	if (debug > 2)
945 		report(LOG_INFO, "bootfile=\"%s\"", clntpath);
946 
947 #ifdef	CHECK_FILE_ACCESS
948 null_file_name:
949 #endif	/* CHECK_FILE_ACCESS */
950 
951 
952 	/*
953 	 * Handle vendor options based on magic number.
954 	 */
955 
956 	if (debug > 1) {
957 		report(LOG_INFO, "vendor magic field is %d.%d.%d.%d",
958 			   (int) ((bp->bp_vend)[0]),
959 			   (int) ((bp->bp_vend)[1]),
960 			   (int) ((bp->bp_vend)[2]),
961 			   (int) ((bp->bp_vend)[3]));
962 	}
963 	/*
964 	 * If this host isn't set for automatic vendor info then copy the
965 	 * specific cookie into the bootp packet, thus forcing a certain
966 	 * reply format.  Only force reply format if user specified it.
967 	 */
968 	if (hp->flags.vm_cookie) {
969 		/* Slam in the user specified magic number. */
970 		bcopy(hp->vm_cookie, bp->bp_vend, 4);
971 	}
972 	/*
973 	 * Figure out the format for the vendor-specific info.
974 	 * Note that bp->bp_vend may have been set above.
975 	 */
976 	if (!bcmp(bp->bp_vend, vm_rfc1048, 4)) {
977 		/* RFC1048 conformant bootp client */
978 		dovend_rfc1048(bp, hp, bootsize);
979 		if (debug > 1) {
980 			report(LOG_INFO, "sending reply (with RFC1048 options)");
981 		}
982 	}
983 #ifdef VEND_CMU
984 	else if (!bcmp(bp->bp_vend, vm_cmu, 4)) {
985 		dovend_cmu(bp, hp);
986 		if (debug > 1) {
987 			report(LOG_INFO, "sending reply (with CMU options)");
988 		}
989 	}
990 #endif
991 	else {
992 		if (debug > 1) {
993 			report(LOG_INFO, "sending reply (with no options)");
994 		}
995 	}
996 
997 	dest = (hp->flags.reply_addr) ?
998 		hp->reply_addr.s_addr : 0L;
999 
1000 	/* not forwarded */
1001 	sendreply(0, dest);
1002 }
1003 
1004 
1005 /*
1006  * Process BOOTREPLY packet.
1007  */
1008 PRIVATE void
1009 handle_reply()
1010 {
1011 	if (debug) {
1012 		report(LOG_INFO, "processing boot reply");
1013 	}
1014 	/* forwarded, no destination override */
1015 	sendreply(1, 0);
1016 }
1017 
1018 
1019 /*
1020  * Send a reply packet to the client.  'forward' flag is set if we are
1021  * not the originator of this reply packet.
1022  */
1023 PRIVATE void
1024 sendreply(forward, dst_override)
1025 	int forward;
1026 	int32 dst_override;
1027 {
1028 	struct bootp *bp = (struct bootp *) pktbuf;
1029 	struct in_addr dst;
1030 	u_short port = bootpc_port;
1031 	unsigned char *ha;
1032 	int len, haf;
1033 
1034 	/*
1035 	 * XXX - Should honor bp_flags "broadcast" bit here.
1036 	 * Temporary workaround: use the :ra=ADDR: option to
1037 	 * set the reply address to the broadcast address.
1038 	 */
1039 
1040 	/*
1041 	 * If the destination address was specified explicitly
1042 	 * (i.e. the broadcast address for HP compatibility)
1043 	 * then send the response to that address.  Otherwise,
1044 	 * act in accordance with RFC951:
1045 	 *   If the client IP address is specified, use that
1046 	 * else if gateway IP address is specified, use that
1047 	 * else make a temporary arp cache entry for the client's
1048 	 * NEW IP/hardware address and use that.
1049 	 */
1050 	if (dst_override) {
1051 		dst.s_addr = dst_override;
1052 		if (debug > 1) {
1053 			report(LOG_INFO, "reply address override: %s",
1054 				   inet_ntoa(dst));
1055 		}
1056 	} else if (bp->bp_ciaddr.s_addr) {
1057 		dst = bp->bp_ciaddr;
1058 	} else if (bp->bp_giaddr.s_addr && forward == 0) {
1059 		dst = bp->bp_giaddr;
1060 		port = bootps_port;
1061 		if (debug > 1) {
1062 			report(LOG_INFO, "sending reply to gateway %s",
1063 				   inet_ntoa(dst));
1064 		}
1065 	} else {
1066 		dst = bp->bp_yiaddr;
1067 		ha = bp->bp_chaddr;
1068 		len = bp->bp_hlen;
1069 		if (len > MAXHADDRLEN)
1070 			len = MAXHADDRLEN;
1071 		haf = (int) bp->bp_htype;
1072 		if (haf == 0)
1073 			haf = HTYPE_ETHERNET;
1074 
1075 		if (arpmod) {
1076 			if (debug > 1)
1077 				report(LOG_INFO, "setarp %s - %s",
1078 					   inet_ntoa(dst), haddrtoa(ha, len));
1079 			setarp(s, &dst, haf, ha, len);
1080 		}
1081 	}
1082 
1083 	if ((forward == 0) &&
1084 		(bp->bp_siaddr.s_addr == 0))
1085 	{
1086 		struct ifreq *ifr;
1087 		struct in_addr siaddr;
1088 		/*
1089 		 * If we are originating this reply, we
1090 		 * need to find our own interface address to
1091 		 * put in the bp_siaddr field of the reply.
1092 		 * If this server is multi-homed, pick the
1093 		 * 'best' interface (the one on the same net
1094 		 * as the client).  Of course, the client may
1095 		 * be on the other side of a BOOTP gateway...
1096 		 */
1097 		ifr = getif(s, &dst);
1098 		if (ifr) {
1099 			struct sockaddr_in *sip;
1100 			sip = (struct sockaddr_in *) &(ifr->ifr_addr);
1101 			siaddr = sip->sin_addr;
1102 		} else {
1103 			/* Just use my "official" IP address. */
1104 			siaddr = my_ip_addr;
1105 		}
1106 
1107 		/* XXX - No need to set bp_giaddr here. */
1108 
1109 		/* Finally, set the server address field. */
1110 		bp->bp_siaddr = siaddr;
1111 	}
1112 	/* Set up socket address for send. */
1113 	send_addr.sin_family = AF_INET;
1114 	send_addr.sin_port = htons(port);
1115 	send_addr.sin_addr = dst;
1116 
1117 	/* Send reply with same size packet as request used. */
1118 	if (sendto(s, pktbuf, pktlen, 0,
1119 			   (struct sockaddr *) &send_addr,
1120 			   sizeof(send_addr)) < 0)
1121 	{
1122 		report(LOG_ERR, "sendto: %s", get_network_errmsg());
1123 	}
1124 } /* sendreply */
1125 
1126 
1127 /* nmatch() - now in getif.c */
1128 /* setarp() - now in hwaddr.c */
1129 
1130 
1131 /*
1132  * This call checks read access to a file.  It returns 0 if the file given
1133  * by "path" exists and is publicly readable.  A value of -1 is returned if
1134  * access is not permitted or an error occurs.  Successful calls also
1135  * return the file size in bytes using the long pointer "filesize".
1136  *
1137  * The read permission bit for "other" users is checked.  This bit must be
1138  * set for tftpd(8) to allow clients to read the file.
1139  */
1140 
1141 PRIVATE int
1142 chk_access(path, filesize)
1143 	char *path;
1144 	int32 *filesize;
1145 {
1146 	struct stat st;
1147 
1148 	if ((stat(path, &st) == 0) && (st.st_mode & (S_IREAD >> 6))) {
1149 		*filesize = (int32) st.st_size;
1150 		return 0;
1151 	} else {
1152 		return -1;
1153 	}
1154 }
1155 
1156 
1157 /*
1158  * Now in dumptab.c :
1159  *	dumptab()
1160  *	dump_host()
1161  *	list_ipaddresses()
1162  */
1163 
1164 #ifdef VEND_CMU
1165 
1166 /*
1167  * Insert the CMU "vendor" data for the host pointed to by "hp" into the
1168  * bootp packet pointed to by "bp".
1169  */
1170 
1171 PRIVATE void
1172 dovend_cmu(bp, hp)
1173 	struct bootp *bp;
1174 	struct host *hp;
1175 {
1176 	struct cmu_vend *vendp;
1177 	struct in_addr_list *taddr;
1178 
1179 	/*
1180 	 * Initialize the entire vendor field to zeroes.
1181 	 */
1182 	bzero(bp->bp_vend, sizeof(bp->bp_vend));
1183 
1184 	/*
1185 	 * Fill in vendor information. Subnet mask, default gateway,
1186 	 * domain name server, ien name server, time server
1187 	 */
1188 	vendp = (struct cmu_vend *) bp->bp_vend;
1189 	strcpy(vendp->v_magic, (char *)vm_cmu);
1190 	if (hp->flags.subnet_mask) {
1191 		(vendp->v_smask).s_addr = hp->subnet_mask.s_addr;
1192 		(vendp->v_flags) |= VF_SMASK;
1193 		if (hp->flags.gateway) {
1194 			(vendp->v_dgate).s_addr = hp->gateway->addr->s_addr;
1195 		}
1196 	}
1197 	if (hp->flags.domain_server) {
1198 		taddr = hp->domain_server;
1199 		if (taddr->addrcount > 0) {
1200 			(vendp->v_dns1).s_addr = (taddr->addr)[0].s_addr;
1201 			if (taddr->addrcount > 1) {
1202 				(vendp->v_dns2).s_addr = (taddr->addr)[1].s_addr;
1203 			}
1204 		}
1205 	}
1206 	if (hp->flags.name_server) {
1207 		taddr = hp->name_server;
1208 		if (taddr->addrcount > 0) {
1209 			(vendp->v_ins1).s_addr = (taddr->addr)[0].s_addr;
1210 			if (taddr->addrcount > 1) {
1211 				(vendp->v_ins2).s_addr = (taddr->addr)[1].s_addr;
1212 			}
1213 		}
1214 	}
1215 	if (hp->flags.time_server) {
1216 		taddr = hp->time_server;
1217 		if (taddr->addrcount > 0) {
1218 			(vendp->v_ts1).s_addr = (taddr->addr)[0].s_addr;
1219 			if (taddr->addrcount > 1) {
1220 				(vendp->v_ts2).s_addr = (taddr->addr)[1].s_addr;
1221 			}
1222 		}
1223 	}
1224 	/* Log message now done by caller. */
1225 } /* dovend_cmu */
1226 
1227 #endif /* VEND_CMU */
1228 
1229 
1230 
1231 /*
1232  * Insert the RFC1048 vendor data for the host pointed to by "hp" into the
1233  * bootp packet pointed to by "bp".
1234  */
1235 #define	NEED(LEN, MSG) do \
1236 	if (bytesleft < (LEN)) { \
1237 		report(LOG_NOTICE, noroom, \
1238 			   hp->hostname->string, MSG); \
1239 		return; \
1240 	} while (0)
1241 PRIVATE void
1242 dovend_rfc1048(bp, hp, bootsize)
1243 	struct bootp *bp;
1244 	struct host *hp;
1245 	int32 bootsize;
1246 {
1247 	int bytesleft, len;
1248 	byte *vp;
1249 
1250 	static const char noroom[] = "%s: No room for \"%s\" option";
1251 
1252 	vp = bp->bp_vend;
1253 
1254 	if (hp->flags.msg_size) {
1255 		pktlen = hp->msg_size;
1256 	} else {
1257 		/*
1258 		 * If the request was longer than the official length, build
1259 		 * a response of that same length where the additional length
1260 		 * is assumed to be part of the bp_vend (options) area.
1261 		 */
1262 		if (pktlen > sizeof(*bp)) {
1263 			if (debug > 1)
1264 				report(LOG_INFO, "request message length=%d", pktlen);
1265 		}
1266 		/*
1267 		 * Check whether the request contains the option:
1268 		 * Maximum DHCP Message Size (RFC1533 sec. 9.8)
1269 		 * and if so, override the response length with its value.
1270 		 * This request must lie within the first BP_VEND_LEN
1271 		 * bytes of the option space.
1272 		 */
1273 		{
1274 			byte *p, *ep;
1275 			byte tag, len;
1276 			short msgsz = 0;
1277 
1278 			p = vp + 4;
1279 			ep = p + BP_VEND_LEN - 4;
1280 			while (p < ep) {
1281 				tag = *p++;
1282 				/* Check for tags with no data first. */
1283 				if (tag == TAG_PAD)
1284 					continue;
1285 				if (tag == TAG_END)
1286 					break;
1287 				/* Now scan the length byte. */
1288 				len = *p++;
1289 				switch (tag) {
1290 				case TAG_MAX_MSGSZ:
1291 					if (len == 2) {
1292 						bcopy(p, (char*)&msgsz, 2);
1293 						msgsz = ntohs(msgsz);
1294 					}
1295 					break;
1296 				case TAG_SUBNET_MASK:
1297 					/* XXX - Should preserve this if given... */
1298 					break;
1299 				} /* swtich */
1300 				p += len;
1301 			}
1302 
1303 			if (msgsz > sizeof(*bp) + BP_MSG_OVERHEAD) {
1304 				if (debug > 1)
1305 					report(LOG_INFO, "request has DHCP msglen=%d", msgsz);
1306 				pktlen = msgsz - BP_MSG_OVERHEAD;
1307 			}
1308 		}
1309 	}
1310 
1311 	if (pktlen < sizeof(*bp)) {
1312 		report(LOG_ERR, "invalid response length=%d", pktlen);
1313 		pktlen = sizeof(*bp);
1314 	}
1315 	bytesleft = ((byte*)bp + pktlen) - vp;
1316 	if (pktlen > sizeof(*bp)) {
1317 		if (debug > 1)
1318 			report(LOG_INFO, "extended reply, length=%d, options=%d",
1319 				   pktlen, bytesleft);
1320 	}
1321 
1322 	/* Copy in the magic cookie */
1323 	bcopy(vm_rfc1048, vp, 4);
1324 	vp += 4;
1325 	bytesleft -= 4;
1326 
1327 	if (hp->flags.subnet_mask) {
1328 		/* always enough room here. */
1329 		*vp++ = TAG_SUBNET_MASK;/* -1 byte  */
1330 		*vp++ = 4;				/* -1 byte  */
1331 		insert_u_long(hp->subnet_mask.s_addr, &vp);	/* -4 bytes */
1332 		bytesleft -= 6;			/* Fix real count */
1333 		if (hp->flags.gateway) {
1334 			(void) insert_ip(TAG_GATEWAY,
1335 							 hp->gateway,
1336 							 &vp, &bytesleft);
1337 		}
1338 	}
1339 	if (hp->flags.bootsize) {
1340 		/* always enough room here */
1341 		bootsize = (hp->flags.bootsize_auto) ?
1342 			((bootsize + 511) / 512) : (hp->bootsize);	/* Round up */
1343 		*vp++ = TAG_BOOT_SIZE;
1344 		*vp++ = 2;
1345 		*vp++ = (byte) ((bootsize >> 8) & 0xFF);
1346 		*vp++ = (byte) (bootsize & 0xFF);
1347 		bytesleft -= 4;			/* Tag, length, and 16 bit blocksize */
1348 	}
1349 	/*
1350 	 * This one is special: Remaining options go in the ext file.
1351 	 * Only the subnet_mask, bootsize, and gateway should precede.
1352 	 */
1353 	if (hp->flags.exten_file) {
1354 		/*
1355 		 * Check for room for exten_file.  Add 3 to account for
1356 		 * TAG_EXTEN_FILE, length, and TAG_END.
1357 		 */
1358 		len = strlen(hp->exten_file->string);
1359 		NEED((len + 3), "ef");
1360 		*vp++ = TAG_EXTEN_FILE;
1361 		*vp++ = (byte) (len & 0xFF);
1362 		bcopy(hp->exten_file->string, vp, len);
1363 		vp += len;
1364 		*vp++ = TAG_END;
1365 		bytesleft -= len + 3;
1366 		return;					/* no more options here. */
1367 	}
1368 	/*
1369 	 * The remaining options are inserted by the following
1370 	 * function (which is shared with bootpef.c).
1371 	 * Keep back one byte for the TAG_END.
1372 	 */
1373 	len = dovend_rfc1497(hp, vp, bytesleft - 1);
1374 	vp += len;
1375 	bytesleft -= len;
1376 
1377 	/* There should be at least one byte left. */
1378 	NEED(1, "(end)");
1379 	*vp++ = TAG_END;
1380 	bytesleft--;
1381 
1382 	/* Log message done by caller. */
1383 	if (bytesleft > 0) {
1384 		/*
1385 		 * Zero out any remaining part of the vendor area.
1386 		 */
1387 		bzero(vp, bytesleft);
1388 	}
1389 } /* dovend_rfc1048 */
1390 #undef	NEED
1391 
1392 
1393 /*
1394  * Now in readfile.c:
1395  * 	hwlookcmp()
1396  *	iplookcmp()
1397  */
1398 
1399 /* haddrtoa() - now in hwaddr.c */
1400 /*
1401  * Now in dovend.c:
1402  * insert_ip()
1403  * insert_generic()
1404  * insert_u_long()
1405  */
1406 
1407 /* get_errmsg() - now in report.c */
1408 
1409 /*
1410  * Local Variables:
1411  * tab-width: 4
1412  * c-indent-level: 4
1413  * c-argdecl-indent: 4
1414  * c-continued-statement-offset: 4
1415  * c-continued-brace-offset: -4
1416  * c-label-offset: -4
1417  * c-brace-offset: 0
1418  * End:
1419  */
1420