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