xref: /freebsd/libexec/bootpd/bootpd.c (revision f39bffc62c1395bde25d152c7f68fdf7cbaab414)
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 	bp->bp_file[sizeof(bp->bp_file)-1] = '\0';
640 
641 	/* XXX - SLIP init: Set bp_ciaddr = recv_addr here? */
642 
643 	/*
644 	 * If the servername field is set, compare it against us.
645 	 * If we're not being addressed, ignore this request.
646 	 * If the server name field is null, throw in our name.
647 	 */
648 	if (strlen(bp->bp_sname)) {
649 		if (strcmp(bp->bp_sname, hostname)) {
650 			if (debug)
651 				report(LOG_INFO, "\
652 ignoring request for server %s from client at %s address %s",
653 					   bp->bp_sname, netname(bp->bp_htype),
654 					   haddrtoa(bp->bp_chaddr, bp->bp_hlen));
655 			/* XXX - Is it correct to ignore such a request? -gwr */
656 			return;
657 		}
658 	} else {
659 		strcpy(bp->bp_sname, hostname);
660 	}
661 
662 	/* Convert the request into a reply. */
663 	bp->bp_op = BOOTREPLY;
664 	if (bp->bp_ciaddr.s_addr == 0) {
665 		/*
666 		 * client doesn't know his IP address,
667 		 * search by hardware address.
668 		 */
669 		if (debug > 1) {
670 			report(LOG_INFO, "request from %s address %s",
671 				   netname(bp->bp_htype),
672 				   haddrtoa(bp->bp_chaddr, bp->bp_hlen));
673 		}
674 		hlen = haddrlength(bp->bp_htype);
675 		if (hlen != bp->bp_hlen) {
676 			report(LOG_NOTICE, "bad addr len from %s address %s",
677 				   netname(bp->bp_htype),
678 				   haddrtoa(bp->bp_chaddr, hlen));
679 		}
680 		dummyhost.htype = bp->bp_htype;
681 		bcopy(bp->bp_chaddr, dummyhost.haddr, hlen);
682 		hashcode = hash_HashFunction(bp->bp_chaddr, hlen);
683 		hp = (struct host *) hash_Lookup(hwhashtable, hashcode, hwlookcmp,
684 										 &dummyhost);
685 		if (hp == NULL &&
686 			bp->bp_htype == HTYPE_IEEE802)
687 		{
688 			/* Try again with address in "canonical" form. */
689 			haddr_conv802(bp->bp_chaddr, dummyhost.haddr, hlen);
690 			if (debug > 1) {
691 				report(LOG_INFO, "\
692 HW addr type is IEEE 802.  convert to %s and check again\n",
693 					   haddrtoa(dummyhost.haddr, bp->bp_hlen));
694 			}
695 			hashcode = hash_HashFunction(dummyhost.haddr, hlen);
696 			hp = (struct host *) hash_Lookup(hwhashtable, hashcode,
697 											 hwlookcmp, &dummyhost);
698 		}
699 		if (hp == NULL) {
700 			/*
701 			 * XXX - Add dynamic IP address assignment?
702 			 */
703 			if (debug)
704 				report(LOG_NOTICE, "unknown client %s address %s",
705 					   netname(bp->bp_htype),
706 					   haddrtoa(bp->bp_chaddr, bp->bp_hlen));
707 			return; /* not found */
708 		}
709 		(bp->bp_yiaddr).s_addr = hp->iaddr.s_addr;
710 
711 	} else {
712 
713 		/*
714 		 * search by IP address.
715 		 */
716 		if (debug > 1) {
717 			report(LOG_INFO, "request from IP addr %s",
718 				   inet_ntoa(bp->bp_ciaddr));
719 		}
720 		dummyhost.iaddr.s_addr = bp->bp_ciaddr.s_addr;
721 		hashcode = hash_HashFunction((u_char *) &(bp->bp_ciaddr.s_addr), 4);
722 		hp = (struct host *) hash_Lookup(iphashtable, hashcode, iplookcmp,
723 										 &dummyhost);
724 		if (hp == NULL) {
725 			if (debug) {
726 				report(LOG_NOTICE, "IP address not found: %s",
727 					   inet_ntoa(bp->bp_ciaddr));
728 			}
729 			return;
730 		}
731 	}
732 
733 	if (debug) {
734 		report(LOG_INFO, "found %s (%s)", inet_ntoa(hp->iaddr),
735 			   hp->hostname->string);
736 	}
737 
738 	/*
739 	 * If there is a response delay threshold, ignore requests
740 	 * with a timestamp lower than the threshold.
741 	 */
742 	if (hp->flags.min_wait) {
743 		u_int32 t = (u_int32) ntohs(bp->bp_secs);
744 		if (t < hp->min_wait) {
745 			if (debug > 1)
746 				report(LOG_INFO,
747 					   "ignoring request due to timestamp (%d < %d)",
748 					   t, hp->min_wait);
749 			return;
750 		}
751 	}
752 
753 #ifdef	YORK_EX_OPTION
754 	/*
755 	 * The need for the "ex" tag arose out of the need to empty
756 	 * shared networked drives on diskless PCs.  This solution is
757 	 * not very clean but it does work fairly well.
758 	 * Written by Edmund J. Sutcliffe <edmund@york.ac.uk>
759 	 *
760 	 * XXX - This could compromise security if a non-trusted user
761 	 * managed to write an entry in the bootptab with :ex=trojan:
762 	 * so I would leave this turned off unless you need it. -gwr
763 	 */
764 	/* Run a program, passing the client name as a parameter. */
765 	if (hp->flags.exec_file) {
766 		char tst[100];
767 		/* XXX - Check string lengths? -gwr */
768 		strcpy (tst, hp->exec_file->string);
769 		strcat (tst, " ");
770 		strcat (tst, hp->hostname->string);
771 		strcat (tst, " &");
772 		if (debug)
773 			report(LOG_INFO, "executing %s", tst);
774 		system(tst);	/* Hope this finishes soon... */
775 	}
776 #endif	/* YORK_EX_OPTION */
777 
778 	/*
779 	 * If a specific TFTP server address was specified in the bootptab file,
780 	 * fill it in, otherwise zero it.
781 	 * XXX - Rather than zero it, should it be the bootpd address? -gwr
782 	 */
783 	(bp->bp_siaddr).s_addr = (hp->flags.bootserver) ?
784 		hp->bootserver.s_addr : 0L;
785 
786 #ifdef	STANFORD_PROM_COMPAT
787 	/*
788 	 * Stanford bootp PROMs (for a Sun?) have no way to leave
789 	 * the boot file name field blank (because the boot file
790 	 * name is automatically generated from some index).
791 	 * As a work-around, this little hack allows those PROMs to
792 	 * specify "sunboot14" with the same effect as a NULL name.
793 	 * (The user specifies boot device 14 or some such magic.)
794 	 */
795 	if (strcmp(bp->bp_file, "sunboot14") == 0)
796 		bp->bp_file[0] = '\0';	/* treat it as unspecified */
797 #endif
798 
799 	/*
800 	 * Fill in the client's proper bootfile.
801 	 *
802 	 * If the client specifies an absolute path, try that file with a
803 	 * ".host" suffix and then without.  If the file cannot be found, no
804 	 * reply is made at all.
805 	 *
806 	 * If the client specifies a null or relative file, use the following
807 	 * table to determine the appropriate action:
808 	 *
809 	 *  Homedir      Bootfile    Client's file
810 	 * specified?   specified?   specification   Action
811 	 * -------------------------------------------------------------------
812 	 *      No          No          Null         Send null filename
813 	 *      No          No          Relative     Discard request
814 	 *      No          Yes         Null         Send if absolute else null
815 	 *      No          Yes         Relative     Discard request     *XXX
816 	 *      Yes         No          Null         Send null filename
817 	 *      Yes         No          Relative     Lookup with ".host"
818 	 *      Yes         Yes         Null         Send home/boot or bootfile
819 	 *      Yes         Yes         Relative     Lookup with ".host" *XXX
820 	 *
821 	 */
822 
823 	/*
824 	 * XXX - I don't like the policy of ignoring a client when the
825 	 * boot file is not accessible.  The TFTP server might not be
826 	 * running on the same machine as the BOOTP server, in which
827 	 * case checking accessibility of the boot file is pointless.
828 	 *
829 	 * Therefore, file accessibility is now demanded ONLY if you
830 	 * define CHECK_FILE_ACCESS in the Makefile options. -gwr
831 	 */
832 
833 	/*
834 	 * The "real" path is as seen by the BOOTP daemon on this
835 	 * machine, while the client path is relative to the TFTP
836 	 * daemon chroot directory (i.e. /tftpboot).
837 	 */
838 	if (hp->flags.tftpdir) {
839 		snprintf(realpath, sizeof(realpath), "%s", hp->tftpdir->string);
840 		clntpath = &realpath[strlen(realpath)];
841 	} else {
842 		realpath[0] = '\0';
843 		clntpath = realpath;
844 	}
845 
846 	/*
847 	 * Determine client's requested homedir and bootfile.
848 	 */
849 	homedir = NULL;
850 	bootfile = NULL;
851 	if (bp->bp_file[0]) {
852 		homedir = bp->bp_file;
853 		bootfile = strrchr(homedir, '/');
854 		if (bootfile) {
855 			if (homedir == bootfile)
856 				homedir = NULL;
857 			*bootfile++ = '\0';
858 		} else {
859 			/* no "/" in the string */
860 			bootfile = homedir;
861 			homedir = NULL;
862 		}
863 		if (debug > 2) {
864 			report(LOG_INFO, "requested path=\"%s\"  file=\"%s\"",
865 				   (homedir) ? homedir : "",
866 				   (bootfile) ? bootfile : "");
867 		}
868 	}
869 
870 	/*
871 	 * Specifications in bootptab override client requested values.
872 	 */
873 	if (hp->flags.homedir)
874 		homedir = hp->homedir->string;
875 	if (hp->flags.bootfile)
876 		bootfile = hp->bootfile->string;
877 
878 	/*
879 	 * Construct bootfile path.
880 	 */
881 	if (homedir) {
882 		if (homedir[0] != '/')
883 			strcat(clntpath, "/");
884 		strcat(clntpath, homedir);
885 		homedir = NULL;
886 	}
887 	if (bootfile) {
888 		if (bootfile[0] != '/')
889 			strcat(clntpath, "/");
890 		strcat(clntpath, bootfile);
891 		bootfile = NULL;
892 	}
893 
894 	/*
895 	 * First try to find the file with a ".host" suffix
896 	 */
897 	n = strlen(clntpath);
898 	strcat(clntpath, ".");
899 	strcat(clntpath, hp->hostname->string);
900 	if (chk_access(realpath, &bootsize) < 0) {
901 		clntpath[n] = 0;			/* Try it without the suffix */
902 		if (chk_access(realpath, &bootsize) < 0) {
903 			/* neither "file.host" nor "file" was found */
904 #ifdef	CHECK_FILE_ACCESS
905 
906 			if (bp->bp_file[0]) {
907 				/*
908 				 * Client wanted specific file
909 				 * and we didn't have it.
910 				 */
911 				report(LOG_NOTICE,
912 					   "requested file not found: \"%s\"", clntpath);
913 				return;
914 			}
915 			/*
916 			 * Client didn't ask for a specific file and we couldn't
917 			 * access the default file, so just zero-out the bootfile
918 			 * field in the packet and continue processing the reply.
919 			 */
920 			bzero(bp->bp_file, sizeof(bp->bp_file));
921 			goto null_file_name;
922 
923 #else	/* CHECK_FILE_ACCESS */
924 
925 			/* Complain only if boot file size was needed. */
926 			if (hp->flags.bootsize_auto) {
927 				report(LOG_ERR, "can not determine size of file \"%s\"",
928 					   clntpath);
929 			}
930 
931 #endif	/* CHECK_FILE_ACCESS */
932 		}
933 	}
934 	strncpy(bp->bp_file, clntpath, BP_FILE_LEN);
935 	if (debug > 2)
936 		report(LOG_INFO, "bootfile=\"%s\"", clntpath);
937 
938 #ifdef	CHECK_FILE_ACCESS
939 null_file_name:
940 #endif	/* CHECK_FILE_ACCESS */
941 
942 
943 	/*
944 	 * Handle vendor options based on magic number.
945 	 */
946 
947 	if (debug > 1) {
948 		report(LOG_INFO, "vendor magic field is %d.%d.%d.%d",
949 			   (int) ((bp->bp_vend)[0]),
950 			   (int) ((bp->bp_vend)[1]),
951 			   (int) ((bp->bp_vend)[2]),
952 			   (int) ((bp->bp_vend)[3]));
953 	}
954 	/*
955 	 * If this host isn't set for automatic vendor info then copy the
956 	 * specific cookie into the bootp packet, thus forcing a certain
957 	 * reply format.  Only force reply format if user specified it.
958 	 */
959 	if (hp->flags.vm_cookie) {
960 		/* Slam in the user specified magic number. */
961 		bcopy(hp->vm_cookie, bp->bp_vend, 4);
962 	}
963 	/*
964 	 * Figure out the format for the vendor-specific info.
965 	 * Note that bp->bp_vend may have been set above.
966 	 */
967 	if (!bcmp(bp->bp_vend, vm_rfc1048, 4)) {
968 		/* RFC1048 conformant bootp client */
969 		dovend_rfc1048(bp, hp, bootsize);
970 		if (debug > 1) {
971 			report(LOG_INFO, "sending reply (with RFC1048 options)");
972 		}
973 	}
974 #ifdef VEND_CMU
975 	else if (!bcmp(bp->bp_vend, vm_cmu, 4)) {
976 		dovend_cmu(bp, hp);
977 		if (debug > 1) {
978 			report(LOG_INFO, "sending reply (with CMU options)");
979 		}
980 	}
981 #endif
982 	else {
983 		if (debug > 1) {
984 			report(LOG_INFO, "sending reply (with no options)");
985 		}
986 	}
987 
988 	dest = (hp->flags.reply_addr) ?
989 		hp->reply_addr.s_addr : 0L;
990 
991 	/* not forwarded */
992 	sendreply(0, dest);
993 }
994 
995 
996 /*
997  * Process BOOTREPLY packet.
998  */
999 PRIVATE void
1000 handle_reply()
1001 {
1002 	if (debug) {
1003 		report(LOG_INFO, "processing boot reply");
1004 	}
1005 	/* forwarded, no destination override */
1006 	sendreply(1, 0);
1007 }
1008 
1009 
1010 /*
1011  * Send a reply packet to the client.  'forward' flag is set if we are
1012  * not the originator of this reply packet.
1013  */
1014 PRIVATE void
1015 sendreply(forward, dst_override)
1016 	int forward;
1017 	int32 dst_override;
1018 {
1019 	struct bootp *bp = (struct bootp *) pktbuf;
1020 	struct in_addr dst;
1021 	u_short port = bootpc_port;
1022 	unsigned char *ha;
1023 	int len, haf;
1024 
1025 	/*
1026 	 * XXX - Should honor bp_flags "broadcast" bit here.
1027 	 * Temporary workaround: use the :ra=ADDR: option to
1028 	 * set the reply address to the broadcast address.
1029 	 */
1030 
1031 	/*
1032 	 * If the destination address was specified explicitly
1033 	 * (i.e. the broadcast address for HP compatibility)
1034 	 * then send the response to that address.  Otherwise,
1035 	 * act in accordance with RFC951:
1036 	 *   If the client IP address is specified, use that
1037 	 * else if gateway IP address is specified, use that
1038 	 * else make a temporary arp cache entry for the client's
1039 	 * NEW IP/hardware address and use that.
1040 	 */
1041 	if (dst_override) {
1042 		dst.s_addr = dst_override;
1043 		if (debug > 1) {
1044 			report(LOG_INFO, "reply address override: %s",
1045 				   inet_ntoa(dst));
1046 		}
1047 	} else if (bp->bp_ciaddr.s_addr) {
1048 		dst = bp->bp_ciaddr;
1049 	} else if (bp->bp_giaddr.s_addr && forward == 0) {
1050 		dst = bp->bp_giaddr;
1051 		port = bootps_port;
1052 		if (debug > 1) {
1053 			report(LOG_INFO, "sending reply to gateway %s",
1054 				   inet_ntoa(dst));
1055 		}
1056 	} else {
1057 		dst = bp->bp_yiaddr;
1058 		ha = bp->bp_chaddr;
1059 		len = bp->bp_hlen;
1060 		if (len > MAXHADDRLEN)
1061 			len = MAXHADDRLEN;
1062 		haf = (int) bp->bp_htype;
1063 		if (haf == 0)
1064 			haf = HTYPE_ETHERNET;
1065 
1066 		if (debug > 1)
1067 			report(LOG_INFO, "setarp %s - %s",
1068 				   inet_ntoa(dst), haddrtoa(ha, len));
1069 		setarp(s, &dst, haf, ha, len);
1070 	}
1071 
1072 	if ((forward == 0) &&
1073 		(bp->bp_siaddr.s_addr == 0))
1074 	{
1075 		struct ifreq *ifr;
1076 		struct in_addr siaddr;
1077 		/*
1078 		 * If we are originating this reply, we
1079 		 * need to find our own interface address to
1080 		 * put in the bp_siaddr field of the reply.
1081 		 * If this server is multi-homed, pick the
1082 		 * 'best' interface (the one on the same net
1083 		 * as the client).  Of course, the client may
1084 		 * be on the other side of a BOOTP gateway...
1085 		 */
1086 		ifr = getif(s, &dst);
1087 		if (ifr) {
1088 			struct sockaddr_in *sip;
1089 			sip = (struct sockaddr_in *) &(ifr->ifr_addr);
1090 			siaddr = sip->sin_addr;
1091 		} else {
1092 			/* Just use my "official" IP address. */
1093 			siaddr = my_ip_addr;
1094 		}
1095 
1096 		/* XXX - No need to set bp_giaddr here. */
1097 
1098 		/* Finally, set the server address field. */
1099 		bp->bp_siaddr = siaddr;
1100 	}
1101 	/* Set up socket address for send. */
1102 	send_addr.sin_family = AF_INET;
1103 	send_addr.sin_port = htons(port);
1104 	send_addr.sin_addr = dst;
1105 
1106 	/* Send reply with same size packet as request used. */
1107 	if (sendto(s, pktbuf, pktlen, 0,
1108 			   (struct sockaddr *) &send_addr,
1109 			   sizeof(send_addr)) < 0)
1110 	{
1111 		report(LOG_ERR, "sendto: %s", get_network_errmsg());
1112 	}
1113 } /* sendreply */
1114 
1115 
1116 /* nmatch() - now in getif.c */
1117 /* setarp() - now in hwaddr.c */
1118 
1119 
1120 /*
1121  * This call checks read access to a file.  It returns 0 if the file given
1122  * by "path" exists and is publicly readable.  A value of -1 is returned if
1123  * access is not permitted or an error occurs.  Successful calls also
1124  * return the file size in bytes using the long pointer "filesize".
1125  *
1126  * The read permission bit for "other" users is checked.  This bit must be
1127  * set for tftpd(8) to allow clients to read the file.
1128  */
1129 
1130 PRIVATE int
1131 chk_access(path, filesize)
1132 	char *path;
1133 	int32 *filesize;
1134 {
1135 	struct stat st;
1136 
1137 	if ((stat(path, &st) == 0) && (st.st_mode & (S_IREAD >> 6))) {
1138 		*filesize = (int32) st.st_size;
1139 		return 0;
1140 	} else {
1141 		return -1;
1142 	}
1143 }
1144 
1145 
1146 /*
1147  * Now in dumptab.c :
1148  *	dumptab()
1149  *	dump_host()
1150  *	list_ipaddresses()
1151  */
1152 
1153 #ifdef VEND_CMU
1154 
1155 /*
1156  * Insert the CMU "vendor" data for the host pointed to by "hp" into the
1157  * bootp packet pointed to by "bp".
1158  */
1159 
1160 PRIVATE void
1161 dovend_cmu(bp, hp)
1162 	struct bootp *bp;
1163 	struct host *hp;
1164 {
1165 	struct cmu_vend *vendp;
1166 	struct in_addr_list *taddr;
1167 
1168 	/*
1169 	 * Initialize the entire vendor field to zeroes.
1170 	 */
1171 	bzero(bp->bp_vend, sizeof(bp->bp_vend));
1172 
1173 	/*
1174 	 * Fill in vendor information. Subnet mask, default gateway,
1175 	 * domain name server, ien name server, time server
1176 	 */
1177 	vendp = (struct cmu_vend *) bp->bp_vend;
1178 	strcpy(vendp->v_magic, (char *)vm_cmu);
1179 	if (hp->flags.subnet_mask) {
1180 		(vendp->v_smask).s_addr = hp->subnet_mask.s_addr;
1181 		(vendp->v_flags) |= VF_SMASK;
1182 		if (hp->flags.gateway) {
1183 			(vendp->v_dgate).s_addr = hp->gateway->addr->s_addr;
1184 		}
1185 	}
1186 	if (hp->flags.domain_server) {
1187 		taddr = hp->domain_server;
1188 		if (taddr->addrcount > 0) {
1189 			(vendp->v_dns1).s_addr = (taddr->addr)[0].s_addr;
1190 			if (taddr->addrcount > 1) {
1191 				(vendp->v_dns2).s_addr = (taddr->addr)[1].s_addr;
1192 			}
1193 		}
1194 	}
1195 	if (hp->flags.name_server) {
1196 		taddr = hp->name_server;
1197 		if (taddr->addrcount > 0) {
1198 			(vendp->v_ins1).s_addr = (taddr->addr)[0].s_addr;
1199 			if (taddr->addrcount > 1) {
1200 				(vendp->v_ins2).s_addr = (taddr->addr)[1].s_addr;
1201 			}
1202 		}
1203 	}
1204 	if (hp->flags.time_server) {
1205 		taddr = hp->time_server;
1206 		if (taddr->addrcount > 0) {
1207 			(vendp->v_ts1).s_addr = (taddr->addr)[0].s_addr;
1208 			if (taddr->addrcount > 1) {
1209 				(vendp->v_ts2).s_addr = (taddr->addr)[1].s_addr;
1210 			}
1211 		}
1212 	}
1213 	/* Log message now done by caller. */
1214 } /* dovend_cmu */
1215 
1216 #endif /* VEND_CMU */
1217 
1218 
1219 
1220 /*
1221  * Insert the RFC1048 vendor data for the host pointed to by "hp" into the
1222  * bootp packet pointed to by "bp".
1223  */
1224 #define	NEED(LEN, MSG) do \
1225 	if (bytesleft < (LEN)) { \
1226 		report(LOG_NOTICE, noroom, \
1227 			   hp->hostname->string, MSG); \
1228 		return; \
1229 	} while (0)
1230 PRIVATE void
1231 dovend_rfc1048(bp, hp, bootsize)
1232 	struct bootp *bp;
1233 	struct host *hp;
1234 	int32 bootsize;
1235 {
1236 	int bytesleft, len;
1237 	byte *vp;
1238 
1239 	static const char noroom[] = "%s: No room for \"%s\" option";
1240 
1241 	vp = bp->bp_vend;
1242 
1243 	if (hp->flags.msg_size) {
1244 		pktlen = hp->msg_size;
1245 	} else {
1246 		/*
1247 		 * If the request was longer than the official length, build
1248 		 * a response of that same length where the additional length
1249 		 * is assumed to be part of the bp_vend (options) area.
1250 		 */
1251 		if (pktlen > sizeof(*bp)) {
1252 			if (debug > 1)
1253 				report(LOG_INFO, "request message length=%d", pktlen);
1254 		}
1255 		/*
1256 		 * Check whether the request contains the option:
1257 		 * Maximum DHCP Message Size (RFC1533 sec. 9.8)
1258 		 * and if so, override the response length with its value.
1259 		 * This request must lie within the first BP_VEND_LEN
1260 		 * bytes of the option space.
1261 		 */
1262 		{
1263 			byte *p, *ep;
1264 			byte tag, len;
1265 			short msgsz = 0;
1266 
1267 			p = vp + 4;
1268 			ep = p + BP_VEND_LEN - 4;
1269 			while (p < ep) {
1270 				tag = *p++;
1271 				/* Check for tags with no data first. */
1272 				if (tag == TAG_PAD)
1273 					continue;
1274 				if (tag == TAG_END)
1275 					break;
1276 				/* Now scan the length byte. */
1277 				len = *p++;
1278 				switch (tag) {
1279 				case TAG_MAX_MSGSZ:
1280 					if (len == 2) {
1281 						bcopy(p, (char*)&msgsz, 2);
1282 						msgsz = ntohs(msgsz);
1283 					}
1284 					break;
1285 				case TAG_SUBNET_MASK:
1286 					/* XXX - Should preserve this if given... */
1287 					break;
1288 				} /* swtich */
1289 				p += len;
1290 			}
1291 
1292 			if (msgsz > sizeof(*bp) + BP_MSG_OVERHEAD) {
1293 				if (debug > 1)
1294 					report(LOG_INFO, "request has DHCP msglen=%d", msgsz);
1295 				pktlen = msgsz - BP_MSG_OVERHEAD;
1296 			}
1297 		}
1298 	}
1299 
1300 	if (pktlen < sizeof(*bp)) {
1301 		report(LOG_ERR, "invalid response length=%d", pktlen);
1302 		pktlen = sizeof(*bp);
1303 	}
1304 	bytesleft = ((byte*)bp + pktlen) - vp;
1305 	if (pktlen > sizeof(*bp)) {
1306 		if (debug > 1)
1307 			report(LOG_INFO, "extended reply, length=%d, options=%d",
1308 				   pktlen, bytesleft);
1309 	}
1310 
1311 	/* Copy in the magic cookie */
1312 	bcopy(vm_rfc1048, vp, 4);
1313 	vp += 4;
1314 	bytesleft -= 4;
1315 
1316 	if (hp->flags.subnet_mask) {
1317 		/* always enough room here. */
1318 		*vp++ = TAG_SUBNET_MASK;/* -1 byte  */
1319 		*vp++ = 4;				/* -1 byte  */
1320 		insert_u_long(hp->subnet_mask.s_addr, &vp);	/* -4 bytes */
1321 		bytesleft -= 6;			/* Fix real count */
1322 		if (hp->flags.gateway) {
1323 			(void) insert_ip(TAG_GATEWAY,
1324 							 hp->gateway,
1325 							 &vp, &bytesleft);
1326 		}
1327 	}
1328 	if (hp->flags.bootsize) {
1329 		/* always enough room here */
1330 		bootsize = (hp->flags.bootsize_auto) ?
1331 			((bootsize + 511) / 512) : (hp->bootsize);	/* Round up */
1332 		*vp++ = TAG_BOOT_SIZE;
1333 		*vp++ = 2;
1334 		*vp++ = (byte) ((bootsize >> 8) & 0xFF);
1335 		*vp++ = (byte) (bootsize & 0xFF);
1336 		bytesleft -= 4;			/* Tag, length, and 16 bit blocksize */
1337 	}
1338 	/*
1339 	 * This one is special: Remaining options go in the ext file.
1340 	 * Only the subnet_mask, bootsize, and gateway should precede.
1341 	 */
1342 	if (hp->flags.exten_file) {
1343 		/*
1344 		 * Check for room for exten_file.  Add 3 to account for
1345 		 * TAG_EXTEN_FILE, length, and TAG_END.
1346 		 */
1347 		len = strlen(hp->exten_file->string);
1348 		NEED((len + 3), "ef");
1349 		*vp++ = TAG_EXTEN_FILE;
1350 		*vp++ = (byte) (len & 0xFF);
1351 		bcopy(hp->exten_file->string, vp, len);
1352 		vp += len;
1353 		*vp++ = TAG_END;
1354 		bytesleft -= len + 3;
1355 		return;					/* no more options here. */
1356 	}
1357 	/*
1358 	 * The remaining options are inserted by the following
1359 	 * function (which is shared with bootpef.c).
1360 	 * Keep back one byte for the TAG_END.
1361 	 */
1362 	len = dovend_rfc1497(hp, vp, bytesleft - 1);
1363 	vp += len;
1364 	bytesleft -= len;
1365 
1366 	/* There should be at least one byte left. */
1367 	NEED(1, "(end)");
1368 	*vp++ = TAG_END;
1369 	bytesleft--;
1370 
1371 	/* Log message done by caller. */
1372 	if (bytesleft > 0) {
1373 		/*
1374 		 * Zero out any remaining part of the vendor area.
1375 		 */
1376 		bzero(vp, bytesleft);
1377 	}
1378 } /* dovend_rfc1048 */
1379 #undef	NEED
1380 
1381 
1382 /*
1383  * Now in readfile.c:
1384  * 	hwlookcmp()
1385  *	iplookcmp()
1386  */
1387 
1388 /* haddrtoa() - now in hwaddr.c */
1389 /*
1390  * Now in dovend.c:
1391  * insert_ip()
1392  * insert_generic()
1393  * insert_u_long()
1394  */
1395 
1396 /* get_errmsg() - now in report.c */
1397 
1398 /*
1399  * Local Variables:
1400  * tab-width: 4
1401  * c-indent-level: 4
1402  * c-argdecl-indent: 4
1403  * c-continued-statement-offset: 4
1404  * c-continued-brace-offset: -4
1405  * c-label-offset: -4
1406  * c-brace-offset: 0
1407  * End:
1408  */
1409