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