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