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