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