Index: head/libexec/bootpd/bootpd.8 =================================================================== --- head/libexec/bootpd/bootpd.8 (revision 348065) +++ head/libexec/bootpd/bootpd.8 (revision 348066) @@ -1,310 +1,312 @@ .\" Copyright (c) 1988, 1989, 1991 Carnegie Mellon University .\" .\" $FreeBSD$ .\" -.Dd February 10, 2004 +.Dd May 21, 2019 .Dt BOOTPD 8 .Os .Sh NAME .Nm bootpd , bootpgw .Nd Internet Boot Protocol server/gateway .Sh SYNOPSIS .Nm .Op Fl i | s .Op Fl c Ar chdir-path .Op Fl d Ar level .Op Fl h Ar hostname .Op Fl t Ar timeout .Oo .Ar bootptab .Op Ar dumpfile .Oc .Nm bootpgw .Op Fl i | s .Op Fl d Ar level .Op Fl h Ar hostname .Op Fl t Ar timeout .Ar server .Sh DESCRIPTION The .Nm utility implements an Internet Bootstrap Protocol (BOOTP) server as defined in RFC951, RFC1532, and RFC1533. The .Nm bootpgw utility implements a simple BOOTP gateway which can be used to forward requests and responses between clients on one subnet and a BOOTP server (i.e.\& .Nm ) on another subnet. While either .Nm or .Nm bootpgw will forward BOOTREPLY packets, only .Nm bootpgw will forward BOOTREQUEST packets. .Pp One host on each network segment is normally configured to run either .Nm or .Nm bootpgw from .Xr inetd 8 by including one of the following lines in the file .Pa /etc/inetd.conf : .Pp .Dl bootps dgram udp wait root /usr/libexec/bootpd bootpd /etc/bootptab .Dl bootps dgram udp wait root /usr/libexec/bootpgw bootpgw server .Pp This mode of operation is referred to as "inetd mode" and causes .Nm (or .Nm bootpgw ) to be started only when a boot request arrives. If it does not receive another packet within fifteen minutes of the last one it received, it will exit to conserve system resources. The .Fl t option controls this timeout (see OPTIONS). .Pp It is also possible to run .Nm (or .Nm bootpgw ) in "standalone mode" (without .Xr inetd 8 ) by simply invoking it from a shell like any other regular command. Standalone mode is particularly useful when .Nm is used with a large configuration database, where the start up delay might otherwise prevent timely response to client requests. (Automatic start up in standalone mode can be done by invoking .Nm from within .Pa /etc/rc.local , for example.) Standalone mode is less useful for .Nm bootpgw which has very little start up delay because it does not read a configuration file. .Pp Either program automatically detects whether it was invoked from inetd or from a shell and automatically selects the appropriate mode. The .Fl s or .Fl i option may be used to force standalone or inetd mode respectively (see OPTIONS). .Sh OPTIONS The following options are available: .Bl -tag -width indent +.It Fl a +Skip ARP table modifications. .It Fl t Ar timeout Specify the .Ar timeout value (in minutes) that a .Nm or .Nm bootpgw process will wait for a BOOTP packet before exiting. If no packets are received for .Ar timeout minutes, then the program will exit. A timeout value of zero means "run forever". In standalone mode, this option is forced to zero. .It Fl d Ar debug-level Set the .Ar debug-level variable that controls the amount of debugging messages generated. For example, .Fl d Ns 4 or .Fl d 4 will set the debugging level to 4. For compatibility with older versions of .Nm , omitting the numeric parameter (i.e., just .Fl d ) will simply increment the debug level by one. .It Fl c Ar chdir-path Set the current directory used by .Nm while checking the existence and size of client boot files. This is useful when client boot files are specified as relative pathnames, and .Nm needs to use the same current directory as the TFTP server (typically .Pa /tftpboot ) . This option is not recognized by .Nm bootpgw . .It Fl h Ar hostname Specify the hostname corresponding to the IP address to listen on. By default, .Nm listens on the IP address corresponding to the machine's hostname, as returned by .Xr gethostname 3 . .It Fl i Force inetd mode. This option is obsolete, but remains for compatibility with older versions of .Nm . .It Fl s Force standalone mode. This option is obsolete, but remains for compatibility with older versions of .Nm . .It Ar bootptab Specify the name of the configuration file from which .Nm loads its database of known clients and client options .No ( Nm only). .It Ar dumpfile Specify the name of the file that .Nm will dump its internal database into when it receives a SIGUSR1 signal .No ( Nm only). This option is only recognized if .Nm was compiled with the -DDEBUG flag. .It Ar server Specify the name of a BOOTP server to which .Nm bootpgw will forward all BOOTREQUEST packets it receives .Pf ( Nm bootpgw only). .El .Sh OPERATION Both .Nm and .Nm bootpgw operate similarly in that both listen for any packets sent to the .Em bootps port, and both simply forward any BOOTREPLY packets. They differ in their handling of BOOTREQUEST packets. .Pp When .Nm bootpgw is started, it determines the address of a BOOTP server whose name is provided as a command line parameter. When .Nm bootpgw receives a BOOTREQUEST packet, it sets the "gateway address" and "hop count" fields in the packet and forwards the packet to the BOOTP server at the address determined earlier. Requests are forwarded only if they indicate that the client has been waiting for at least three seconds. .Pp When .Nm is started it reads a configuration file, (normally .Pa /etc/bootptab ) that initializes the internal database of known clients and client options. This internal database is reloaded from the configuration file when .Nm receives a hangup signal (SIGHUP) or when it discovers that the configuration file has changed. .Pp When .Nm receives a BOOTREQUEST packet, it .\" checks the modification time of the .\" configuration file and reloads the database if necessary. Then it looks for a database entry matching the client request. If the client is known, .Nm composes a BOOTREPLY packet using the database entry found above, and sends the reply to the client (possibly using a gateway). If the client is unknown, the request is discarded (with a notice if debug > 0). .Pp If .Nm is compiled with the -DDEBUG option, receipt of a SIGUSR1 signal causes it to dump its internal database to the file .Pa /tmp/bootpd.dump or the dumpfile specified as a command line parameter. .Pp During initialization, both programs determine the UDP port numbers to be used by calling .Xr getservbyname 3 (which normally uses .Pa /etc/services ) . Two service names (and port numbers) are used: .Pp .Dl bootps BOOTP Server listening port .Dl bootpc BOOTP Client destination port .Pp If the port numbers cannot be determined using .Xr getservbyname 3 then the values default to bootps=67 and bootpc=68. .Sh FILES .Bl -tag -width /tmp/bootpd.dump -compact .It Pa /etc/bootptab Database file read by .Nm . .It Pa /tmp/bootpd.dump Debugging dump file created by .Nm . .It Pa /etc/services Internet service numbers. .It Pa /tftpboot Current directory typically used by the TFTP server and .Nm . .El .Sh "SEE ALSO" .Xr bootptab 5 , .Xr inetd 8 , .Xr tftpd 8 .Pp DARPA Internet Request For Comments: .Bl -tag -width RFC1533 -compact .It RFC951 Bootstrap Protocol .It RFC1532 Clarifications and Extensions for the Bootstrap Protocol .It RFC1533 DHCP Options and BOOTP Vendor Extensions .El .Sh AUTHORS This distribution is currently maintained by .An Walter L. Wimer Aq Mt walt+@cmu.edu . .Pp The original BOOTP server was created by .An Bill Croft at Stanford University in January 1986. .Pp The current version of .Nm is primarily the work of .An David Kovar , .An Drew D. Perkins , and .An Walter L. Wimer , at Carnegie Mellon University. .Pp Enhancements and bug-fixes have been contributed by: .Pp (in alphabetical order) .Pp .An -split .An Danny Backx Aq Mt db@sunbim.be .An John Brezak Aq Mt brezak@ch.hp.com .An Frank da Cruz Aq Mt fdc@cc.columbia.edu .An David R. Linn Aq Mt drl@vuse.vanderbilt.edu .An Jim McKim Aq Mt mckim@lerc.nasa.gov .An Gordon W. Ross Aq Mt gwr@mc.com .An Jason Zions Aq Mt jazz@hal.com . .Sh BUGS Individual host entries must not exceed 1024 characters. Index: head/libexec/bootpd/bootpd.c =================================================================== --- head/libexec/bootpd/bootpd.c (revision 348065) +++ head/libexec/bootpd/bootpd.c (revision 348066) @@ -1,1412 +1,1419 @@ /************************************************************************ Copyright 1988, 1991 by Carnegie Mellon University All Rights Reserved Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Carnegie Mellon University not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ************************************************************************/ /* * BOOTP (bootstrap protocol) server daemon. * * Answers BOOTP request packets from booting client machines. * See [SRI-NIC]RFC951.TXT for a description of the protocol. * See [SRI-NIC]RFC1048.TXT for vendor-information extensions. * See RFC 1395 for option tags 14-17. * See accompanying man page -- bootpd.8 * * HISTORY * See ./Changes * * BUGS * See ./ToDo */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include /* inet_ntoa */ #ifndef NO_UNISTD #include #endif #include #include #include #include #include #include #include #include #include #include #include #ifdef NO_SETSID # include /* for O_RDONLY, etc */ #endif #ifndef USE_BFUNCS # include /* Yes, memcpy is OK here (no overlapped copies). */ # define bcopy(a,b,c) memcpy(b,a,c) # define bzero(p,l) memset(p,0,l) # define bcmp(a,b,c) memcmp(a,b,c) #endif #include "bootp.h" #include "hash.h" #include "hwaddr.h" #include "bootpd.h" #include "dovend.h" #include "getif.h" #include "readfile.h" #include "report.h" #include "tzone.h" #include "patchlevel.h" #ifndef CONFIG_FILE #define CONFIG_FILE "/etc/bootptab" #endif #ifndef DUMPTAB_FILE #define DUMPTAB_FILE "/tmp/bootpd.dump" #endif /* * Externals, forward declarations, and global variables */ extern void dumptab(char *); PRIVATE void catcher(int); PRIVATE int chk_access(char *, int32 *); #ifdef VEND_CMU PRIVATE void dovend_cmu(struct bootp *, struct host *); #endif PRIVATE void dovend_rfc1048(struct bootp *, struct host *, int32); PRIVATE void handle_reply(void); PRIVATE void handle_request(void); PRIVATE void sendreply(int forward, int32 dest_override); PRIVATE void usage(void); /* * IP port numbers for client and server obtained from /etc/services */ u_short bootps_port, bootpc_port; /* * Internet socket and interface config structures */ struct sockaddr_in bind_addr; /* Listening */ struct sockaddr_in recv_addr; /* Packet source */ struct sockaddr_in send_addr; /* destination */ /* * option defaults */ int debug = 0; /* Debugging flag (level) */ struct timeval actualtimeout = { /* fifteen minutes */ 15 * 60L, /* tv_sec */ 0 /* tv_usec */ }; +int arpmod = TRUE; /* modify the ARP table */ /* * General */ int s; /* Socket file descriptor */ char *pktbuf; /* Receive packet buffer */ int pktlen; char *progname; char *chdir_path; struct in_addr my_ip_addr; static const char *hostname; static char default_hostname[MAXHOSTNAMELEN]; /* Flags set by signal catcher. */ PRIVATE int do_readtab = 0; PRIVATE int do_dumptab = 0; /* * Globals below are associated with the bootp database file (bootptab). */ char *bootptab = CONFIG_FILE; char *bootpd_dump = DUMPTAB_FILE; /* * Initialization such as command-line processing is done and then the * main server loop is started. */ int main(argc, argv) int argc; char **argv; { struct timeval *timeout; struct bootp *bp; struct servent *servp; struct hostent *hep; char *stmp; socklen_t ba_len, ra_len; int n; int nfound; fd_set readfds; int standalone; #ifdef SA_NOCLDSTOP /* Have POSIX sigaction(2). */ struct sigaction sa; #endif progname = strrchr(argv[0], '/'); if (progname) progname++; else progname = argv[0]; /* * Initialize logging. */ report_init(0); /* uses progname */ /* * Log startup */ report(LOG_INFO, "version %s.%d", VERSION, PATCHLEVEL); /* Debugging for compilers with struct padding. */ assert(sizeof(struct bootp) == BP_MINPKTSZ); /* Get space for receiving packets and composing replies. */ pktbuf = malloc(MAX_MSG_SIZE); if (!pktbuf) { report(LOG_ERR, "malloc failed"); exit(1); } bp = (struct bootp *) pktbuf; /* * Check to see if a socket was passed to us from inetd. * * Use getsockname() to determine if descriptor 0 is indeed a socket * (and thus we are probably a child of inetd) or if it is instead * something else and we are running standalone. */ s = 0; ba_len = sizeof(bind_addr); bzero((char *) &bind_addr, ba_len); errno = 0; standalone = TRUE; if (getsockname(s, (struct sockaddr *) &bind_addr, &ba_len) == 0) { /* * Descriptor 0 is a socket. Assume we are a child of inetd. */ if (bind_addr.sin_family == AF_INET) { standalone = FALSE; bootps_port = ntohs(bind_addr.sin_port); } else { /* Some other type of socket? */ report(LOG_ERR, "getsockname: not an INET socket"); } } /* * Set defaults that might be changed by option switches. */ stmp = NULL; timeout = &actualtimeout; if (gethostname(default_hostname, sizeof(default_hostname) - 1) < 0) { report(LOG_ERR, "bootpd: can't get hostname\n"); exit(1); } default_hostname[sizeof(default_hostname) - 1] = '\0'; hostname = default_hostname; /* * Read switches. */ for (argc--, argv++; argc > 0; argc--, argv++) { if (argv[0][0] != '-') break; switch (argv[0][1]) { + case 'a': /* don't modify the ARP table */ + arpmod = FALSE; + break; case 'c': /* chdir_path */ if (argv[0][2]) { stmp = &(argv[0][2]); } else { argc--; argv++; stmp = argv[0]; } if (!stmp || (stmp[0] != '/')) { report(LOG_ERR, "bootpd: invalid chdir specification\n"); break; } chdir_path = stmp; break; case 'd': /* debug level */ if (argv[0][2]) { stmp = &(argv[0][2]); } else if (argv[1] && argv[1][0] == '-') { /* * Backwards-compatible behavior: * no parameter, so just increment the debug flag. */ debug++; break; } else { argc--; argv++; stmp = argv[0]; } if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) { report(LOG_ERR, "%s: invalid debug level\n", progname); break; } debug = n; break; case 'h': /* override hostname */ if (argv[0][2]) { stmp = &(argv[0][2]); } else { argc--; argv++; stmp = argv[0]; } if (!stmp) { report(LOG_ERR, "bootpd: missing hostname\n"); break; } hostname = stmp; break; case 'i': /* inetd mode */ standalone = FALSE; break; case 's': /* standalone mode */ standalone = TRUE; break; case 't': /* timeout */ if (argv[0][2]) { stmp = &(argv[0][2]); } else { argc--; argv++; stmp = argv[0]; } if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) { report(LOG_ERR, "%s: invalid timeout specification\n", progname); break; } actualtimeout.tv_sec = (int32) (60 * n); /* * If the actual timeout is zero, pass a NULL pointer * to select so it blocks indefinitely, otherwise, * point to the actual timeout value. */ timeout = (n > 0) ? &actualtimeout : NULL; break; default: report(LOG_ERR, "%s: unknown switch: -%c\n", progname, argv[0][1]); usage(); break; } /* switch */ } /* for args */ /* * Override default file names if specified on the command line. */ if (argc > 0) bootptab = argv[0]; if (argc > 1) bootpd_dump = argv[1]; /* * Get my hostname and IP address. */ hep = gethostbyname(hostname); if (!hep) { report(LOG_ERR, "Can not get my IP address\n"); exit(1); } bcopy(hep->h_addr, (char *)&my_ip_addr, sizeof(my_ip_addr)); if (standalone) { /* * Go into background and disassociate from controlling terminal. */ if (debug < 3) { if (fork()) exit(0); #ifdef NO_SETSID setpgrp(0,0); #ifdef TIOCNOTTY n = open(_PATH_TTY, O_RDWR); if (n >= 0) { ioctl(n, TIOCNOTTY, (char *) 0); (void) close(n); } #endif /* TIOCNOTTY */ #else /* SETSID */ if (setsid() < 0) perror("setsid"); #endif /* SETSID */ } /* if debug < 3 */ /* * Nuke any timeout value */ timeout = NULL; } /* if standalone (1st) */ /* Set the cwd (i.e. to /tftpboot) */ if (chdir_path) { if (chdir(chdir_path) < 0) report(LOG_ERR, "%s: chdir failed", chdir_path); } /* Get the timezone. */ tzone_init(); /* Allocate hash tables. */ rdtab_init(); /* * Read the bootptab file. */ readtab(1); /* force read */ if (standalone) { /* * Create a socket. */ if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { report(LOG_ERR, "socket: %s", get_network_errmsg()); exit(1); } /* * Get server's listening port number */ servp = getservbyname("bootps", "udp"); if (servp) { bootps_port = ntohs((u_short) servp->s_port); } else { bootps_port = (u_short) IPPORT_BOOTPS; report(LOG_ERR, "bootps/udp: unknown service -- using port %d", bootps_port); } /* * Bind socket to BOOTPS port. */ bind_addr.sin_family = AF_INET; bind_addr.sin_addr.s_addr = INADDR_ANY; bind_addr.sin_port = htons(bootps_port); if (bind(s, (struct sockaddr *) &bind_addr, sizeof(bind_addr)) < 0) { report(LOG_ERR, "bind: %s", get_network_errmsg()); exit(1); } } /* if standalone (2nd)*/ /* * Get destination port number so we can reply to client */ servp = getservbyname("bootpc", "udp"); if (servp) { bootpc_port = ntohs(servp->s_port); } else { report(LOG_ERR, "bootpc/udp: unknown service -- using port %d", IPPORT_BOOTPC); bootpc_port = (u_short) IPPORT_BOOTPC; } /* * Set up signals to read or dump the table. */ #ifdef SA_NOCLDSTOP /* Have POSIX sigaction(2). */ sa.sa_handler = catcher; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; if (sigaction(SIGHUP, &sa, NULL) < 0) { report(LOG_ERR, "sigaction: %s", get_errmsg()); exit(1); } if (sigaction(SIGUSR1, &sa, NULL) < 0) { report(LOG_ERR, "sigaction: %s", get_errmsg()); exit(1); } #else /* SA_NOCLDSTOP */ /* Old-fashioned UNIX signals */ if ((int) signal(SIGHUP, catcher) < 0) { report(LOG_ERR, "signal: %s", get_errmsg()); exit(1); } if ((int) signal(SIGUSR1, catcher) < 0) { report(LOG_ERR, "signal: %s", get_errmsg()); exit(1); } #endif /* SA_NOCLDSTOP */ /* * Process incoming requests. */ FD_ZERO(&readfds); for (;;) { struct timeval tv; FD_SET(s, &readfds); if (timeout) tv = *timeout; nfound = select(s + 1, &readfds, NULL, NULL, (timeout) ? &tv : NULL); if (nfound < 0) { if (errno != EINTR) { report(LOG_ERR, "select: %s", get_errmsg()); } /* * Call readtab() or dumptab() here to avoid the * dangers of doing I/O from a signal handler. */ if (do_readtab) { do_readtab = 0; readtab(1); /* force read */ } if (do_dumptab) { do_dumptab = 0; dumptab(bootpd_dump); } continue; } if (!FD_ISSET(s, &readfds)) { if (debug > 1) report(LOG_INFO, "exiting after %jd minutes of inactivity", (intmax_t)actualtimeout.tv_sec / 60); exit(0); } ra_len = sizeof(recv_addr); n = recvfrom(s, pktbuf, MAX_MSG_SIZE, 0, (struct sockaddr *) &recv_addr, &ra_len); if (n <= 0) { continue; } if (debug > 1) { report(LOG_INFO, "recvd pkt from IP addr %s", inet_ntoa(recv_addr.sin_addr)); } if (n < sizeof(struct bootp)) { if (debug) { report(LOG_NOTICE, "received short packet"); } continue; } pktlen = n; readtab(0); /* maybe re-read bootptab */ switch (bp->bp_op) { case BOOTREQUEST: handle_request(); break; case BOOTREPLY: handle_reply(); break; } } return 0; } /* * Print "usage" message and exit */ PRIVATE void usage() { fprintf(stderr, - "usage: bootpd [-i | -s] [-c chdir-path] [-d level] [-h hostname] [-t timeout]\n"); + "usage: bootpd [-a] [-i | -s] [-c chdir-path] [-d level] [-h hostname] [-t timeout]\n"); fprintf(stderr, " [bootptab [dumpfile]]\n"); + fprintf(stderr, "\t -a\tdon't modify ARP table\n"); fprintf(stderr, "\t -c n\tset current directory\n"); fprintf(stderr, "\t -d n\tset debug level\n"); fprintf(stderr, "\t -h n\tset the hostname to listen on\n"); fprintf(stderr, "\t -i\tforce inetd mode (run as child of inetd)\n"); fprintf(stderr, "\t -s\tforce standalone mode (run without inetd)\n"); fprintf(stderr, "\t -t n\tset inetd exit timeout to n minutes\n"); exit(1); } /* Signal catchers */ PRIVATE void catcher(sig) int sig; { if (sig == SIGHUP) do_readtab = 1; if (sig == SIGUSR1) do_dumptab = 1; #if !defined(SA_NOCLDSTOP) && defined(SYSV) /* For older "System V" derivatives with no sigaction(). */ signal(sig, catcher); #endif } /* * Process BOOTREQUEST packet. * * Note: This version of the bootpd.c server never forwards * a request to another server. That is the job of a gateway * program such as the "bootpgw" program included here. * * (Also this version does not interpret the hostname field of * the request packet; it COULD do a name->address lookup and * forward the request there.) */ PRIVATE void handle_request() { struct bootp *bp = (struct bootp *) pktbuf; struct host *hp = NULL; struct host dummyhost; int32 bootsize = 0; unsigned hlen, hashcode; int32 dest; char realpath[1024]; char *clntpath; char *homedir, *bootfile; int n; if (bp->bp_htype >= hwinfocnt) { report(LOG_NOTICE, "bad hw addr type %u", bp->bp_htype); return; } bp->bp_file[sizeof(bp->bp_file)-1] = '\0'; /* XXX - SLIP init: Set bp_ciaddr = recv_addr here? */ /* * If the servername field is set, compare it against us. * If we're not being addressed, ignore this request. * If the server name field is null, throw in our name. */ if (strlen(bp->bp_sname)) { if (strcmp(bp->bp_sname, hostname)) { if (debug) report(LOG_INFO, "\ ignoring request for server %s from client at %s address %s", bp->bp_sname, netname(bp->bp_htype), haddrtoa(bp->bp_chaddr, bp->bp_hlen)); /* XXX - Is it correct to ignore such a request? -gwr */ return; } } else { strcpy(bp->bp_sname, hostname); } /* Convert the request into a reply. */ bp->bp_op = BOOTREPLY; if (bp->bp_ciaddr.s_addr == 0) { /* * client doesn't know his IP address, * search by hardware address. */ if (debug > 1) { report(LOG_INFO, "request from %s address %s", netname(bp->bp_htype), haddrtoa(bp->bp_chaddr, bp->bp_hlen)); } hlen = haddrlength(bp->bp_htype); if (hlen != bp->bp_hlen) { report(LOG_NOTICE, "bad addr len from %s address %s", netname(bp->bp_htype), haddrtoa(bp->bp_chaddr, hlen)); } dummyhost.htype = bp->bp_htype; bcopy(bp->bp_chaddr, dummyhost.haddr, hlen); hashcode = hash_HashFunction(bp->bp_chaddr, hlen); hp = (struct host *) hash_Lookup(hwhashtable, hashcode, hwlookcmp, &dummyhost); if (hp == NULL && bp->bp_htype == HTYPE_IEEE802) { /* Try again with address in "canonical" form. */ haddr_conv802(bp->bp_chaddr, dummyhost.haddr, hlen); if (debug > 1) { report(LOG_INFO, "\ HW addr type is IEEE 802. convert to %s and check again\n", haddrtoa(dummyhost.haddr, bp->bp_hlen)); } hashcode = hash_HashFunction(dummyhost.haddr, hlen); hp = (struct host *) hash_Lookup(hwhashtable, hashcode, hwlookcmp, &dummyhost); } if (hp == NULL) { /* * XXX - Add dynamic IP address assignment? */ if (debug) report(LOG_NOTICE, "unknown client %s address %s", netname(bp->bp_htype), haddrtoa(bp->bp_chaddr, bp->bp_hlen)); return; /* not found */ } (bp->bp_yiaddr).s_addr = hp->iaddr.s_addr; } else { /* * search by IP address. */ if (debug > 1) { report(LOG_INFO, "request from IP addr %s", inet_ntoa(bp->bp_ciaddr)); } dummyhost.iaddr.s_addr = bp->bp_ciaddr.s_addr; hashcode = hash_HashFunction((u_char *) &(bp->bp_ciaddr.s_addr), 4); hp = (struct host *) hash_Lookup(iphashtable, hashcode, iplookcmp, &dummyhost); if (hp == NULL) { if (debug) { report(LOG_NOTICE, "IP address not found: %s", inet_ntoa(bp->bp_ciaddr)); } return; } } if (debug) { report(LOG_INFO, "found %s (%s)", inet_ntoa(hp->iaddr), hp->hostname->string); } /* * If there is a response delay threshold, ignore requests * with a timestamp lower than the threshold. */ if (hp->flags.min_wait) { u_int32 t = (u_int32) ntohs(bp->bp_secs); if (t < hp->min_wait) { if (debug > 1) report(LOG_INFO, "ignoring request due to timestamp (%d < %d)", t, hp->min_wait); return; } } #ifdef YORK_EX_OPTION /* * The need for the "ex" tag arose out of the need to empty * shared networked drives on diskless PCs. This solution is * not very clean but it does work fairly well. * Written by Edmund J. Sutcliffe * * XXX - This could compromise security if a non-trusted user * managed to write an entry in the bootptab with :ex=trojan: * so I would leave this turned off unless you need it. -gwr */ /* Run a program, passing the client name as a parameter. */ if (hp->flags.exec_file) { char tst[100]; /* XXX - Check string lengths? -gwr */ strcpy (tst, hp->exec_file->string); strcat (tst, " "); strcat (tst, hp->hostname->string); strcat (tst, " &"); if (debug) report(LOG_INFO, "executing %s", tst); system(tst); /* Hope this finishes soon... */ } #endif /* YORK_EX_OPTION */ /* * If a specific TFTP server address was specified in the bootptab file, * fill it in, otherwise zero it. * XXX - Rather than zero it, should it be the bootpd address? -gwr */ (bp->bp_siaddr).s_addr = (hp->flags.bootserver) ? hp->bootserver.s_addr : 0L; #ifdef STANFORD_PROM_COMPAT /* * Stanford bootp PROMs (for a Sun?) have no way to leave * the boot file name field blank (because the boot file * name is automatically generated from some index). * As a work-around, this little hack allows those PROMs to * specify "sunboot14" with the same effect as a NULL name. * (The user specifies boot device 14 or some such magic.) */ if (strcmp(bp->bp_file, "sunboot14") == 0) bp->bp_file[0] = '\0'; /* treat it as unspecified */ #endif /* * Fill in the client's proper bootfile. * * If the client specifies an absolute path, try that file with a * ".host" suffix and then without. If the file cannot be found, no * reply is made at all. * * If the client specifies a null or relative file, use the following * table to determine the appropriate action: * * Homedir Bootfile Client's file * specified? specified? specification Action * ------------------------------------------------------------------- * No No Null Send null filename * No No Relative Discard request * No Yes Null Send if absolute else null * No Yes Relative Discard request *XXX * Yes No Null Send null filename * Yes No Relative Lookup with ".host" * Yes Yes Null Send home/boot or bootfile * Yes Yes Relative Lookup with ".host" *XXX * */ /* * XXX - I don't like the policy of ignoring a client when the * boot file is not accessible. The TFTP server might not be * running on the same machine as the BOOTP server, in which * case checking accessibility of the boot file is pointless. * * Therefore, file accessibility is now demanded ONLY if you * define CHECK_FILE_ACCESS in the Makefile options. -gwr */ /* * The "real" path is as seen by the BOOTP daemon on this * machine, while the client path is relative to the TFTP * daemon chroot directory (i.e. /tftpboot). */ if (hp->flags.tftpdir) { snprintf(realpath, sizeof(realpath), "%s", hp->tftpdir->string); clntpath = &realpath[strlen(realpath)]; } else { realpath[0] = '\0'; clntpath = realpath; } /* * Determine client's requested homedir and bootfile. */ homedir = NULL; bootfile = NULL; if (bp->bp_file[0]) { homedir = bp->bp_file; bootfile = strrchr(homedir, '/'); if (bootfile) { if (homedir == bootfile) homedir = NULL; *bootfile++ = '\0'; } else { /* no "/" in the string */ bootfile = homedir; homedir = NULL; } if (debug > 2) { report(LOG_INFO, "requested path=\"%s\" file=\"%s\"", (homedir) ? homedir : "", (bootfile) ? bootfile : ""); } } /* * Specifications in bootptab override client requested values. */ if (hp->flags.homedir) homedir = hp->homedir->string; if (hp->flags.bootfile) bootfile = hp->bootfile->string; /* * Construct bootfile path. */ if (homedir) { if (homedir[0] != '/') strcat(clntpath, "/"); strcat(clntpath, homedir); homedir = NULL; } if (bootfile) { if (bootfile[0] != '/') strcat(clntpath, "/"); strcat(clntpath, bootfile); bootfile = NULL; } /* * First try to find the file with a ".host" suffix */ n = strlen(clntpath); strcat(clntpath, "."); strcat(clntpath, hp->hostname->string); if (chk_access(realpath, &bootsize) < 0) { clntpath[n] = 0; /* Try it without the suffix */ if (chk_access(realpath, &bootsize) < 0) { /* neither "file.host" nor "file" was found */ #ifdef CHECK_FILE_ACCESS if (bp->bp_file[0]) { /* * Client wanted specific file * and we didn't have it. */ report(LOG_NOTICE, "requested file not found: \"%s\"", clntpath); return; } /* * Client didn't ask for a specific file and we couldn't * access the default file, so just zero-out the bootfile * field in the packet and continue processing the reply. */ bzero(bp->bp_file, sizeof(bp->bp_file)); goto null_file_name; #else /* CHECK_FILE_ACCESS */ /* Complain only if boot file size was needed. */ if (hp->flags.bootsize_auto) { report(LOG_ERR, "can not determine size of file \"%s\"", clntpath); } #endif /* CHECK_FILE_ACCESS */ } } strncpy(bp->bp_file, clntpath, BP_FILE_LEN); if (debug > 2) report(LOG_INFO, "bootfile=\"%s\"", clntpath); #ifdef CHECK_FILE_ACCESS null_file_name: #endif /* CHECK_FILE_ACCESS */ /* * Handle vendor options based on magic number. */ if (debug > 1) { report(LOG_INFO, "vendor magic field is %d.%d.%d.%d", (int) ((bp->bp_vend)[0]), (int) ((bp->bp_vend)[1]), (int) ((bp->bp_vend)[2]), (int) ((bp->bp_vend)[3])); } /* * If this host isn't set for automatic vendor info then copy the * specific cookie into the bootp packet, thus forcing a certain * reply format. Only force reply format if user specified it. */ if (hp->flags.vm_cookie) { /* Slam in the user specified magic number. */ bcopy(hp->vm_cookie, bp->bp_vend, 4); } /* * Figure out the format for the vendor-specific info. * Note that bp->bp_vend may have been set above. */ if (!bcmp(bp->bp_vend, vm_rfc1048, 4)) { /* RFC1048 conformant bootp client */ dovend_rfc1048(bp, hp, bootsize); if (debug > 1) { report(LOG_INFO, "sending reply (with RFC1048 options)"); } } #ifdef VEND_CMU else if (!bcmp(bp->bp_vend, vm_cmu, 4)) { dovend_cmu(bp, hp); if (debug > 1) { report(LOG_INFO, "sending reply (with CMU options)"); } } #endif else { if (debug > 1) { report(LOG_INFO, "sending reply (with no options)"); } } dest = (hp->flags.reply_addr) ? hp->reply_addr.s_addr : 0L; /* not forwarded */ sendreply(0, dest); } /* * Process BOOTREPLY packet. */ PRIVATE void handle_reply() { if (debug) { report(LOG_INFO, "processing boot reply"); } /* forwarded, no destination override */ sendreply(1, 0); } /* * Send a reply packet to the client. 'forward' flag is set if we are * not the originator of this reply packet. */ PRIVATE void sendreply(forward, dst_override) int forward; int32 dst_override; { struct bootp *bp = (struct bootp *) pktbuf; struct in_addr dst; u_short port = bootpc_port; unsigned char *ha; int len, haf; /* * XXX - Should honor bp_flags "broadcast" bit here. * Temporary workaround: use the :ra=ADDR: option to * set the reply address to the broadcast address. */ /* * If the destination address was specified explicitly * (i.e. the broadcast address for HP compatibility) * then send the response to that address. Otherwise, * act in accordance with RFC951: * If the client IP address is specified, use that * else if gateway IP address is specified, use that * else make a temporary arp cache entry for the client's * NEW IP/hardware address and use that. */ if (dst_override) { dst.s_addr = dst_override; if (debug > 1) { report(LOG_INFO, "reply address override: %s", inet_ntoa(dst)); } } else if (bp->bp_ciaddr.s_addr) { dst = bp->bp_ciaddr; } else if (bp->bp_giaddr.s_addr && forward == 0) { dst = bp->bp_giaddr; port = bootps_port; if (debug > 1) { report(LOG_INFO, "sending reply to gateway %s", inet_ntoa(dst)); } } else { dst = bp->bp_yiaddr; ha = bp->bp_chaddr; len = bp->bp_hlen; if (len > MAXHADDRLEN) len = MAXHADDRLEN; haf = (int) bp->bp_htype; if (haf == 0) haf = HTYPE_ETHERNET; - if (debug > 1) - report(LOG_INFO, "setarp %s - %s", - inet_ntoa(dst), haddrtoa(ha, len)); - setarp(s, &dst, haf, ha, len); + if (arpmod) { + if (debug > 1) + report(LOG_INFO, "setarp %s - %s", + inet_ntoa(dst), haddrtoa(ha, len)); + setarp(s, &dst, haf, ha, len); + } } if ((forward == 0) && (bp->bp_siaddr.s_addr == 0)) { struct ifreq *ifr; struct in_addr siaddr; /* * If we are originating this reply, we * need to find our own interface address to * put in the bp_siaddr field of the reply. * If this server is multi-homed, pick the * 'best' interface (the one on the same net * as the client). Of course, the client may * be on the other side of a BOOTP gateway... */ ifr = getif(s, &dst); if (ifr) { struct sockaddr_in *sip; sip = (struct sockaddr_in *) &(ifr->ifr_addr); siaddr = sip->sin_addr; } else { /* Just use my "official" IP address. */ siaddr = my_ip_addr; } /* XXX - No need to set bp_giaddr here. */ /* Finally, set the server address field. */ bp->bp_siaddr = siaddr; } /* Set up socket address for send. */ send_addr.sin_family = AF_INET; send_addr.sin_port = htons(port); send_addr.sin_addr = dst; /* Send reply with same size packet as request used. */ if (sendto(s, pktbuf, pktlen, 0, (struct sockaddr *) &send_addr, sizeof(send_addr)) < 0) { report(LOG_ERR, "sendto: %s", get_network_errmsg()); } } /* sendreply */ /* nmatch() - now in getif.c */ /* setarp() - now in hwaddr.c */ /* * This call checks read access to a file. It returns 0 if the file given * by "path" exists and is publicly readable. A value of -1 is returned if * access is not permitted or an error occurs. Successful calls also * return the file size in bytes using the long pointer "filesize". * * The read permission bit for "other" users is checked. This bit must be * set for tftpd(8) to allow clients to read the file. */ PRIVATE int chk_access(path, filesize) char *path; int32 *filesize; { struct stat st; if ((stat(path, &st) == 0) && (st.st_mode & (S_IREAD >> 6))) { *filesize = (int32) st.st_size; return 0; } else { return -1; } } /* * Now in dumptab.c : * dumptab() * dump_host() * list_ipaddresses() */ #ifdef VEND_CMU /* * Insert the CMU "vendor" data for the host pointed to by "hp" into the * bootp packet pointed to by "bp". */ PRIVATE void dovend_cmu(bp, hp) struct bootp *bp; struct host *hp; { struct cmu_vend *vendp; struct in_addr_list *taddr; /* * Initialize the entire vendor field to zeroes. */ bzero(bp->bp_vend, sizeof(bp->bp_vend)); /* * Fill in vendor information. Subnet mask, default gateway, * domain name server, ien name server, time server */ vendp = (struct cmu_vend *) bp->bp_vend; strcpy(vendp->v_magic, (char *)vm_cmu); if (hp->flags.subnet_mask) { (vendp->v_smask).s_addr = hp->subnet_mask.s_addr; (vendp->v_flags) |= VF_SMASK; if (hp->flags.gateway) { (vendp->v_dgate).s_addr = hp->gateway->addr->s_addr; } } if (hp->flags.domain_server) { taddr = hp->domain_server; if (taddr->addrcount > 0) { (vendp->v_dns1).s_addr = (taddr->addr)[0].s_addr; if (taddr->addrcount > 1) { (vendp->v_dns2).s_addr = (taddr->addr)[1].s_addr; } } } if (hp->flags.name_server) { taddr = hp->name_server; if (taddr->addrcount > 0) { (vendp->v_ins1).s_addr = (taddr->addr)[0].s_addr; if (taddr->addrcount > 1) { (vendp->v_ins2).s_addr = (taddr->addr)[1].s_addr; } } } if (hp->flags.time_server) { taddr = hp->time_server; if (taddr->addrcount > 0) { (vendp->v_ts1).s_addr = (taddr->addr)[0].s_addr; if (taddr->addrcount > 1) { (vendp->v_ts2).s_addr = (taddr->addr)[1].s_addr; } } } /* Log message now done by caller. */ } /* dovend_cmu */ #endif /* VEND_CMU */ /* * Insert the RFC1048 vendor data for the host pointed to by "hp" into the * bootp packet pointed to by "bp". */ #define NEED(LEN, MSG) do \ if (bytesleft < (LEN)) { \ report(LOG_NOTICE, noroom, \ hp->hostname->string, MSG); \ return; \ } while (0) PRIVATE void dovend_rfc1048(bp, hp, bootsize) struct bootp *bp; struct host *hp; int32 bootsize; { int bytesleft, len; byte *vp; static const char noroom[] = "%s: No room for \"%s\" option"; vp = bp->bp_vend; if (hp->flags.msg_size) { pktlen = hp->msg_size; } else { /* * If the request was longer than the official length, build * a response of that same length where the additional length * is assumed to be part of the bp_vend (options) area. */ if (pktlen > sizeof(*bp)) { if (debug > 1) report(LOG_INFO, "request message length=%d", pktlen); } /* * Check whether the request contains the option: * Maximum DHCP Message Size (RFC1533 sec. 9.8) * and if so, override the response length with its value. * This request must lie within the first BP_VEND_LEN * bytes of the option space. */ { byte *p, *ep; byte tag, len; short msgsz = 0; p = vp + 4; ep = p + BP_VEND_LEN - 4; while (p < ep) { tag = *p++; /* Check for tags with no data first. */ if (tag == TAG_PAD) continue; if (tag == TAG_END) break; /* Now scan the length byte. */ len = *p++; switch (tag) { case TAG_MAX_MSGSZ: if (len == 2) { bcopy(p, (char*)&msgsz, 2); msgsz = ntohs(msgsz); } break; case TAG_SUBNET_MASK: /* XXX - Should preserve this if given... */ break; } /* swtich */ p += len; } if (msgsz > sizeof(*bp) + BP_MSG_OVERHEAD) { if (debug > 1) report(LOG_INFO, "request has DHCP msglen=%d", msgsz); pktlen = msgsz - BP_MSG_OVERHEAD; } } } if (pktlen < sizeof(*bp)) { report(LOG_ERR, "invalid response length=%d", pktlen); pktlen = sizeof(*bp); } bytesleft = ((byte*)bp + pktlen) - vp; if (pktlen > sizeof(*bp)) { if (debug > 1) report(LOG_INFO, "extended reply, length=%d, options=%d", pktlen, bytesleft); } /* Copy in the magic cookie */ bcopy(vm_rfc1048, vp, 4); vp += 4; bytesleft -= 4; if (hp->flags.subnet_mask) { /* always enough room here. */ *vp++ = TAG_SUBNET_MASK;/* -1 byte */ *vp++ = 4; /* -1 byte */ insert_u_long(hp->subnet_mask.s_addr, &vp); /* -4 bytes */ bytesleft -= 6; /* Fix real count */ if (hp->flags.gateway) { (void) insert_ip(TAG_GATEWAY, hp->gateway, &vp, &bytesleft); } } if (hp->flags.bootsize) { /* always enough room here */ bootsize = (hp->flags.bootsize_auto) ? ((bootsize + 511) / 512) : (hp->bootsize); /* Round up */ *vp++ = TAG_BOOT_SIZE; *vp++ = 2; *vp++ = (byte) ((bootsize >> 8) & 0xFF); *vp++ = (byte) (bootsize & 0xFF); bytesleft -= 4; /* Tag, length, and 16 bit blocksize */ } /* * This one is special: Remaining options go in the ext file. * Only the subnet_mask, bootsize, and gateway should precede. */ if (hp->flags.exten_file) { /* * Check for room for exten_file. Add 3 to account for * TAG_EXTEN_FILE, length, and TAG_END. */ len = strlen(hp->exten_file->string); NEED((len + 3), "ef"); *vp++ = TAG_EXTEN_FILE; *vp++ = (byte) (len & 0xFF); bcopy(hp->exten_file->string, vp, len); vp += len; *vp++ = TAG_END; bytesleft -= len + 3; return; /* no more options here. */ } /* * The remaining options are inserted by the following * function (which is shared with bootpef.c). * Keep back one byte for the TAG_END. */ len = dovend_rfc1497(hp, vp, bytesleft - 1); vp += len; bytesleft -= len; /* There should be at least one byte left. */ NEED(1, "(end)"); *vp++ = TAG_END; bytesleft--; /* Log message done by caller. */ if (bytesleft > 0) { /* * Zero out any remaining part of the vendor area. */ bzero(vp, bytesleft); } } /* dovend_rfc1048 */ #undef NEED /* * Now in readfile.c: * hwlookcmp() * iplookcmp() */ /* haddrtoa() - now in hwaddr.c */ /* * Now in dovend.c: * insert_ip() * insert_generic() * insert_u_long() */ /* get_errmsg() - now in report.c */ /* * Local Variables: * tab-width: 4 * c-indent-level: 4 * c-argdecl-indent: 4 * c-continued-statement-offset: 4 * c-continued-brace-offset: -4 * c-label-offset: -4 * c-brace-offset: 0 * End: */ Index: head/libexec/bootpd/bootpgw/bootpgw.c =================================================================== --- head/libexec/bootpd/bootpgw/bootpgw.c (revision 348065) +++ head/libexec/bootpd/bootpgw/bootpgw.c (revision 348066) @@ -1,677 +1,686 @@ /* * bootpgw.c - BOOTP GateWay * This program forwards BOOTP Request packets to a BOOTP server. */ /************************************************************************ Copyright 1988, 1991 by Carnegie Mellon University All Rights Reserved Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of Carnegie Mellon University not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ************************************************************************/ /* * BOOTPGW is typically used to forward BOOTP client requests from * one subnet to a BOOTP server on a different subnet. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include /* inet_ntoa */ #ifndef NO_UNISTD #include #endif #include #include #include #include #include #include #include #include #include #include #include #ifdef NO_SETSID # include /* for O_RDONLY, etc */ #endif #ifndef USE_BFUNCS # include /* Yes, memcpy is OK here (no overlapped copies). */ # define bcopy(a,b,c) memcpy(b,a,c) # define bzero(p,l) memset(p,0,l) # define bcmp(a,b,c) memcmp(a,b,c) #endif #include "bootp.h" #include "getif.h" #include "hwaddr.h" #include "report.h" #include "patchlevel.h" /* Local definitions: */ #define MAX_MSG_SIZE (3*512) /* Maximum packet size */ #define TRUE 1 #define FALSE 0 #define get_network_errmsg get_errmsg /* * Externals, forward declarations, and global variables */ static void usage(void); static void handle_reply(void); static void handle_request(void); /* * IP port numbers for client and server obtained from /etc/services */ u_short bootps_port, bootpc_port; /* * Internet socket and interface config structures */ struct sockaddr_in bind_addr; /* Listening */ struct sockaddr_in recv_addr; /* Packet source */ struct sockaddr_in send_addr; /* destination */ /* * option defaults */ int debug = 0; /* Debugging flag (level) */ struct timeval actualtimeout = { /* fifteen minutes */ 15 * 60L, /* tv_sec */ 0 /* tv_usec */ }; u_char maxhops = 4; /* Number of hops allowed for requests. */ u_int minwait = 3; /* Number of seconds client must wait before its bootrequest packets are forwarded. */ +int arpmod = TRUE; /* modify the ARP table */ /* * General */ int s; /* Socket file descriptor */ char *pktbuf; /* Receive packet buffer */ int pktlen; char *progname; char *servername; int32 server_ipa; /* Real server IP address, network order. */ struct in_addr my_ip_addr; struct utsname my_uname; char *hostname; /* * Initialization such as command-line processing is done and then the * main server loop is started. */ int main(argc, argv) int argc; char **argv; { struct timeval *timeout; struct bootp *bp; struct servent *servp; struct hostent *hep; char *stmp; int n, ba_len, ra_len; int nfound, readfds; int standalone; progname = strrchr(argv[0], '/'); if (progname) progname++; else progname = argv[0]; /* * Initialize logging. */ report_init(0); /* uses progname */ /* * Log startup */ report(LOG_INFO, "version %s.%d", VERSION, PATCHLEVEL); /* Debugging for compilers with struct padding. */ assert(sizeof(struct bootp) == BP_MINPKTSZ); /* Get space for receiving packets and composing replies. */ pktbuf = malloc(MAX_MSG_SIZE); if (!pktbuf) { report(LOG_ERR, "malloc failed"); exit(1); } bp = (struct bootp *) pktbuf; /* * Check to see if a socket was passed to us from inetd. * * Use getsockname() to determine if descriptor 0 is indeed a socket * (and thus we are probably a child of inetd) or if it is instead * something else and we are running standalone. */ s = 0; ba_len = sizeof(bind_addr); bzero((char *) &bind_addr, ba_len); errno = 0; standalone = TRUE; if (getsockname(s, (struct sockaddr *) &bind_addr, &ba_len) == 0) { /* * Descriptor 0 is a socket. Assume we are a child of inetd. */ if (bind_addr.sin_family == AF_INET) { standalone = FALSE; bootps_port = ntohs(bind_addr.sin_port); } else { /* Some other type of socket? */ report(LOG_INFO, "getsockname: not an INET socket"); } } /* * Set defaults that might be changed by option switches. */ stmp = NULL; timeout = &actualtimeout; if (uname(&my_uname) < 0) errx(1, "can't get hostname"); hostname = my_uname.nodename; hep = gethostbyname(hostname); if (!hep) { printf("Can not get my IP address\n"); exit(1); } bcopy(hep->h_addr, (char *)&my_ip_addr, sizeof(my_ip_addr)); /* * Read switches. */ for (argc--, argv++; argc > 0; argc--, argv++) { if (argv[0][0] != '-') break; switch (argv[0][1]) { + case 'a': /* don't modify the ARP table */ + arpmod = FALSE; + break; case 'd': /* debug level */ if (argv[0][2]) { stmp = &(argv[0][2]); } else if (argv[1] && argv[1][0] == '-') { /* * Backwards-compatible behavior: * no parameter, so just increment the debug flag. */ debug++; break; } else { argc--; argv++; stmp = argv[0]; } if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) { warnx("invalid debug level"); break; } debug = n; break; case 'h': /* hop count limit */ if (argv[0][2]) { stmp = &(argv[0][2]); } else { argc--; argv++; stmp = argv[0]; } if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0) || (n > 16)) { warnx("invalid hop count limit"); break; } maxhops = (u_char)n; break; case 'i': /* inetd mode */ standalone = FALSE; break; case 's': /* standalone mode */ standalone = TRUE; break; case 't': /* timeout */ if (argv[0][2]) { stmp = &(argv[0][2]); } else { argc--; argv++; stmp = argv[0]; } if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) { warnx("invalid timeout specification"); break; } actualtimeout.tv_sec = (int32) (60 * n); /* * If the actual timeout is zero, pass a NULL pointer * to select so it blocks indefinitely, otherwise, * point to the actual timeout value. */ timeout = (n > 0) ? &actualtimeout : NULL; break; case 'w': /* wait time */ if (argv[0][2]) { stmp = &(argv[0][2]); } else { argc--; argv++; stmp = argv[0]; } if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0) || (n > 60)) { warnx("invalid wait time"); break; } minwait = (u_int)n; break; default: warnx("unknown switch: -%c", argv[0][1]); usage(); break; } /* switch */ } /* for args */ /* Make sure server name argument is suplied. */ servername = argv[0]; if (!servername) { warnx("missing server name"); usage(); } /* * Get address of real bootp server. */ if (isdigit(servername[0])) server_ipa = inet_addr(servername); else { hep = gethostbyname(servername); if (!hep) errx(1, "can't get addr for %s", servername); bcopy(hep->h_addr, (char *)&server_ipa, sizeof(server_ipa)); } if (standalone) { /* * Go into background and disassociate from controlling terminal. * XXX - This is not the POSIX way (Should use setsid). -gwr */ if (debug < 3) { if (fork()) exit(0); #ifdef NO_SETSID setpgrp(0,0); #ifdef TIOCNOTTY n = open(_PATH_TTY, O_RDWR); if (n >= 0) { ioctl(n, TIOCNOTTY, (char *) 0); (void) close(n); } #endif /* TIOCNOTTY */ #else /* SETSID */ if (setsid() < 0) perror("setsid"); #endif /* SETSID */ } /* if debug < 3 */ /* * Nuke any timeout value */ timeout = NULL; /* * Here, bootpd would do: * chdir * tzone_init * rdtab_init * readtab */ /* * Create a socket. */ if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { report(LOG_ERR, "socket: %s", get_network_errmsg()); exit(1); } /* * Get server's listening port number */ servp = getservbyname("bootps", "udp"); if (servp) { bootps_port = ntohs((u_short) servp->s_port); } else { bootps_port = (u_short) IPPORT_BOOTPS; report(LOG_ERR, "bootps/udp: unknown service -- using port %d", bootps_port); } /* * Bind socket to BOOTPS port. */ bind_addr.sin_family = AF_INET; bind_addr.sin_port = htons(bootps_port); bind_addr.sin_addr.s_addr = INADDR_ANY; if (bind(s, (struct sockaddr *) &bind_addr, sizeof(bind_addr)) < 0) { report(LOG_ERR, "bind: %s", get_network_errmsg()); exit(1); } } /* if standalone */ /* * Get destination port number so we can reply to client */ servp = getservbyname("bootpc", "udp"); if (servp) { bootpc_port = ntohs(servp->s_port); } else { report(LOG_ERR, "bootpc/udp: unknown service -- using port %d", IPPORT_BOOTPC); bootpc_port = (u_short) IPPORT_BOOTPC; } /* no signal catchers */ /* * Process incoming requests. */ for (;;) { struct timeval tv; readfds = 1 << s; if (timeout) tv = *timeout; nfound = select(s + 1, (fd_set *)&readfds, NULL, NULL, (timeout) ? &tv : NULL); if (nfound < 0) { if (errno != EINTR) { report(LOG_ERR, "select: %s", get_errmsg()); } continue; } if (!(readfds & (1 << s))) { report(LOG_INFO, "exiting after %ld minutes of inactivity", (long)(actualtimeout.tv_sec / 60)); exit(0); } ra_len = sizeof(recv_addr); n = recvfrom(s, pktbuf, MAX_MSG_SIZE, 0, (struct sockaddr *) &recv_addr, &ra_len); if (n <= 0) { continue; } if (debug > 3) { report(LOG_INFO, "recvd pkt from IP addr %s", inet_ntoa(recv_addr.sin_addr)); } if (n < sizeof(struct bootp)) { if (debug) { report(LOG_INFO, "received short packet"); } continue; } pktlen = n; switch (bp->bp_op) { case BOOTREQUEST: handle_request(); break; case BOOTREPLY: handle_reply(); break; } } return 0; } /* * Print "usage" message and exit */ static void usage() { fprintf(stderr, - "usage: bootpgw [-d level] [-i] [-s] [-t timeout] server\n"); + "usage: \nbootpgw [-a] [-d level] [-h count] [-i] [-s] [-t timeout] [-w time] server\n"); + fprintf(stderr, "\t -a\tdon't modify ARP table\n"); fprintf(stderr, "\t -d n\tset debug level\n"); fprintf(stderr, "\t -h n\tset max hop count\n"); fprintf(stderr, "\t -i\tforce inetd mode (run as child of inetd)\n"); fprintf(stderr, "\t -s\tforce standalone mode (run without inetd)\n"); fprintf(stderr, "\t -t n\tset inetd exit timeout to n minutes\n"); fprintf(stderr, "\t -w n\tset min wait time (secs)\n"); exit(1); } /* * Process BOOTREQUEST packet. * * Note, this just forwards the request to a real server. */ static void handle_request() { struct bootp *bp = (struct bootp *) pktbuf; u_short secs; u_char hops; /* XXX - SLIP init: Set bp_ciaddr = recv_addr here? */ if (debug) { report(LOG_INFO, "request from %s", inet_ntoa(recv_addr.sin_addr)); } /* Has the client been waiting long enough? */ secs = ntohs(bp->bp_secs); if (secs < minwait) return; /* Has this packet hopped too many times? */ hops = bp->bp_hops; if (++hops > maxhops) { report(LOG_NOTICE, "request from %s reached hop limit", inet_ntoa(recv_addr.sin_addr)); return; } bp->bp_hops = hops; /* * Here one might discard a request from the same subnet as the * real server, but we can assume that the real server will send * a reply to the client before it waits for minwait seconds. */ /* If gateway address is not set, put in local interface addr. */ if (bp->bp_giaddr.s_addr == 0) { #if 0 /* BUG */ struct sockaddr_in *sip; struct ifreq *ifr; /* * XXX - This picks the wrong interface when the receive addr * is the broadcast address. There is no portable way to * find out which interface a broadcast was received on. -gwr * (Thanks to for finding this bug!) */ ifr = getif(s, &recv_addr.sin_addr); if (!ifr) { report(LOG_NOTICE, "no interface for request from %s", inet_ntoa(recv_addr.sin_addr)); return; } sip = (struct sockaddr_in *) &(ifr->ifr_addr); bp->bp_giaddr = sip->sin_addr; #else /* BUG */ /* * XXX - Just set "giaddr" to our "official" IP address. * RFC 1532 says giaddr MUST be set to the address of the * interface on which the request was received. Setting * it to our "default" IP address is not strictly correct, * but is good enough to allow the real BOOTP server to * get the reply back here. Then, before we forward the * reply to the client, the giaddr field is corrected. * (In case the client uses giaddr, which it should not.) * See handle_reply() */ bp->bp_giaddr = my_ip_addr; #endif /* BUG */ /* * XXX - DHCP says to insert a subnet mask option into the * options area of the request (if vendor magic == std). */ } /* Set up socket address for send. */ send_addr.sin_family = AF_INET; send_addr.sin_port = htons(bootps_port); send_addr.sin_addr.s_addr = server_ipa; /* Send reply with same size packet as request used. */ if (sendto(s, pktbuf, pktlen, 0, (struct sockaddr *) &send_addr, sizeof(send_addr)) < 0) { report(LOG_ERR, "sendto: %s", get_network_errmsg()); } } /* * Process BOOTREPLY packet. */ static void handle_reply() { struct bootp *bp = (struct bootp *) pktbuf; struct ifreq *ifr; struct sockaddr_in *sip; unsigned char *ha; int len, haf; if (debug) { report(LOG_INFO, " reply for %s", inet_ntoa(bp->bp_yiaddr)); } /* Make sure client is directly accessible. */ ifr = getif(s, &(bp->bp_yiaddr)); if (!ifr) { report(LOG_NOTICE, "no interface for reply to %s", inet_ntoa(bp->bp_yiaddr)); return; } #if 1 /* Experimental (see BUG above) */ /* #ifdef CATER_TO_OLD_CLIENTS ? */ /* * The giaddr field has been set to our "default" IP address * which might not be on the same interface as the client. * In case the client looks at giaddr, (which it should not) * giaddr is now set to the address of the correct interface. */ sip = (struct sockaddr_in *) &(ifr->ifr_addr); bp->bp_giaddr = sip->sin_addr; #endif /* Set up socket address for send to client. */ send_addr.sin_family = AF_INET; send_addr.sin_addr = bp->bp_yiaddr; send_addr.sin_port = htons(bootpc_port); - /* Create an ARP cache entry for the client. */ - ha = bp->bp_chaddr; - len = bp->bp_hlen; - if (len > MAXHADDRLEN) - len = MAXHADDRLEN; - haf = (int) bp->bp_htype; - if (haf == 0) - haf = HTYPE_ETHERNET; + if (arpmod) { + /* Create an ARP cache entry for the client. */ + ha = bp->bp_chaddr; + len = bp->bp_hlen; + struct in_addr dst; - if (debug > 1) - report(LOG_INFO, "setarp %s - %s", - inet_ntoa(bp->bp_yiaddr), haddrtoa(ha, len)); - setarp(s, &bp->bp_yiaddr, haf, ha, len); + if (len > MAXHADDRLEN) + len = MAXHADDRLEN; + haf = (int) bp->bp_htype; + if (haf == 0) + haf = HTYPE_ETHERNET; + + if (debug > 1) + report(LOG_INFO, "setarp %s - %s", + inet_ntoa(dst), haddrtoa(ha, len)); + setarp(s, &dst, haf, ha, len); + } /* Send reply with same size packet as request used. */ if (sendto(s, pktbuf, pktlen, 0, (struct sockaddr *) &send_addr, sizeof(send_addr)) < 0) { report(LOG_ERR, "sendto: %s", get_network_errmsg()); } } /* * Local Variables: * tab-width: 4 * c-indent-level: 4 * c-argdecl-indent: 4 * c-continued-statement-offset: 4 * c-continued-brace-offset: -4 * c-label-offset: -4 * c-brace-offset: 0 * End: */