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