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