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