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