diff options
Diffstat (limited to 'libexec/bootpd/bootpd.c')
| -rw-r--r-- | libexec/bootpd/bootpd.c | 1729 |
1 files changed, 1729 insertions, 0 deletions
diff --git a/libexec/bootpd/bootpd.c b/libexec/bootpd/bootpd.c new file mode 100644 index 000000000000..dd119e426c3d --- /dev/null +++ b/libexec/bootpd/bootpd.c @@ -0,0 +1,1729 @@ +#ifndef _BLURB_ +#define _BLURB_ +/************************************************************************ + 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. +************************************************************************/ +#endif /* _BLURB_ */ + + +#ifndef lint +static char sccsid[] = "@(#)bootp.c 1.1 (Stanford) 1/22/86"; +static char rcsid[] = "$Header: /home/cvs/386BSD/src/libexec/bootpd/bootpd.c,v 1.1 1994/01/25 22:53:36 martin Exp $"; +#endif + + +/* + * BOOTP (bootstrap protocol) server daemon. + * + * Answers BOOTP request packets from booting client machines. + * See [SRI-NIC]<RFC>RFC951.TXT for a description of the protocol. + * See [SRI-NIC]<RFC>RFC1048.TXT for vendor-information extensions. + * See accompanying man page -- bootpd.8 + * + * + * HISTORY + * + * 01/22/86 Bill Croft at Stanford University + * Created. + * + * 07/30/86 David Kovar at Carnegie Mellon University + * Modified to work at CMU. + * + * 07/24/87 Drew D. Perkins at Carnegie Mellon University + * Modified to use syslog instead of Kovar's + * routines. Add debugging dumps. Many other fixups. + * + * 07/15/88 Walter L. Wimer at Carnegie Mellon University + * Added vendor information to conform to RFC1048. + * Adopted termcap-like file format to support above. + * Added hash table lookup instead of linear search. + * Other cleanups. + * + * + * BUGS + * + * Currently mallocs memory in a very haphazard manner. As such, most of + * the program ends up core-resident all the time just to follow all the + * stupid pointers around. . . . + * + */ + + + + +#include <sys/types.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <sys/file.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <net/if.h> +#ifdef SUNOS40 +#include <sys/sockio.h> +#include <net/if_arp.h> +#endif +#include <netinet/in.h> +#include <signal.h> +#include <stdio.h> +#include <strings.h> +#include <errno.h> +#include <ctype.h> +#include <netdb.h> +#include <syslog.h> + +#include "bootp.h" +#include "hash.h" +#include "bootpd.h" + +#define HASHTABLESIZE 257 /* Hash table size (prime) */ +#define DEFAULT_TIMEOUT 15L /* Default timeout in minutes */ + +#ifndef CONFIG_FILE +#define CONFIG_FILE "/etc/bootptab" +#endif +#ifndef DUMP_FILE +#define DUMP_FILE "/etc/bootpd.dump" +#endif + + + +/* + * Externals, forward declarations, and global variables + */ + +extern char Version[]; +extern char *sys_errlist[]; +extern int errno, sys_nerr; + +void usage(); +void insert_u_long(); +void dump_host(); +void list_ipaddresses(); +#ifdef VEND_CMU +void dovend_cmu(); +#endif +void dovend_rfc1048(); +boolean hwlookcmp(); +boolean iplookcmp(); +void insert_generic(); +void insert_ip(); +int dumptab(); +int chk_access(); +void report(); +char *get_errmsg(); + +/* + * 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 s_in; +struct sockaddr_in from; /* Packet source */ +struct ifreq ifreq[10]; /* Holds interface configuration */ +struct ifconf ifconf; /* Int. config ioctl block (pnts to ifreq) */ +struct arpreq arpreq; /* Arp request ioctl block */ + + +/* + * General + */ + +int debug = 0; /* Debugging flag (level) */ +int s; /* Socket file descriptor */ +byte buf[1024]; /* Receive packet buffer */ +struct timezone tzp; /* Time zone offset for clients */ +struct timeval tp; /* Time (extra baggage) */ +long secondswest; /* Time zone offset in seconds */ +char hostname[MAXHOSTNAMELEN]; /* System host name */ + +/* + * Globals below are associated with the bootp database file (bootptab). + */ + +char *bootptab = NULL; +#ifdef DEBUG +char *bootpd_dump = NULL; +#endif + + + +/* + * Vendor magic cookies for CMU and RFC1048 + */ + +unsigned char vm_cmu[4] = VM_CMU; +unsigned char vm_rfc1048[4] = VM_RFC1048; + + +/* + * Hardware address lengths (in bytes) and network name based on hardware + * type code. List in order specified by Assigned Numbers RFC; Array index + * is hardware type code. Entries marked as zero are unknown to the author + * at this time. . . . + */ + +struct hwinfo hwinfolist[MAXHTYPES + 1] = { + { 0, "Reserved" }, /* Type 0: Reserved (don't use this) */ + { 6, "Ethernet" }, /* Type 1: 10Mb Ethernet (48 bits) */ + { 1, "3Mb Ethernet" }, /* Type 2: 3Mb Ethernet (8 bits) */ + { 0, "AX.25" }, /* Type 3: Amateur Radio AX.25 */ + { 1, "ProNET" }, /* Type 4: Proteon ProNET Token Ring */ + { 0, "Chaos" }, /* Type 5: Chaos */ + { 6, "IEEE 802" }, /* Type 6: IEEE 802 Networks */ + { 0, "ARCNET" } /* Type 7: ARCNET */ +}; + + +/* + * Main hash tables + */ + +hash_tbl *hwhashtable; +hash_tbl *iphashtable; +hash_tbl *nmhashtable; + + + + +/* + * Initialization such as command-line processing is done and then the main + * server loop is started. + */ + +main(argc, argv) + int argc; + char **argv; +{ + struct timeval actualtimeout, *timeout; + struct bootp *bp = (struct bootp *) buf; + struct servent *servp; + char *stmp; + int n, tolen, fromlen; + int nfound, readfds; + int standalone; + + stmp = NULL; + standalone = FALSE; + actualtimeout.tv_usec = 0L; + actualtimeout.tv_sec = 60 * DEFAULT_TIMEOUT; + timeout = &actualtimeout; + + + /* + * Assume 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; + tolen = sizeof(s_in); + bzero((char *) &s_in, tolen); + errno = 0; + if (getsockname(s, &s_in, &tolen) == 0) { + /* + * Descriptor 0 is a socket. Assume we're running as a child of inetd. + */ + bootps_port = ntohs(s_in.sin_port); + standalone = FALSE; + } else { + if (errno == ENOTSOCK) { + /* + * Descriptor 0 is NOT a socket. Run in standalone mode. + */ + standalone = TRUE; + } else { + /* + * Something else went wrong. Punt. + */ + fprintf(stderr, "bootpd: getsockname: %s\n", + get_network_errmsg()); + exit(1); + } + } + + + /* + * Read switches. + */ + for (argc--, argv++; argc > 0; argc--, argv++) { + if (argv[0][0] == '-') { + switch (argv[0][1]) { + case 't': + if (argv[0][2]) { + stmp = &(argv[0][2]); + } else { + argc--; + argv++; + stmp = argv[0]; + } + if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) { + fprintf(stderr, + "bootpd: invalid timeout specification\n"); + break; + } + actualtimeout.tv_sec = (long) (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 'd': + 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)) { + fprintf(stderr, + "bootpd: invalid debug level\n"); + break; + } + debug = n; + break; + case 's': + standalone = TRUE; + break; + case 'i': + standalone = FALSE; + break; + default: + fprintf(stderr, "bootpd: unknown switch: -%c\n", + argv[0][1]); + usage(); + break; + } + } else { + if (!bootptab) { + bootptab = argv[0]; +#ifdef DEBUG + } else if (!bootpd_dump) { + bootpd_dump = argv[0]; +#endif + } else { + fprintf(stderr, "bootpd: unknown argument: %s\n", argv[0]); + usage(); + } + } + } + + /* + * Get hostname. + */ + if (gethostname(hostname, sizeof(hostname)) == -1) { + fprintf(stderr, "bootpd: can't get hostname\n"); + exit(1); + } + + /* + * Set default file names if not specified on command line + */ + if (!bootptab) { + bootptab = CONFIG_FILE; + } +#ifdef DEBUG + if (!bootpd_dump) { + bootpd_dump = DUMP_FILE; + } +#endif + + + if (standalone) { + /* + * Go into background and disassociate from controlling terminal. + */ + if (debug < 3) { + if (fork()) + exit(0); + for (n = 0; n < 10; n++) + (void) close(n); + (void) open("/", O_RDONLY); + (void) dup2(0, 1); + (void) dup2(0, 2); + n = open("/dev/tty", O_RDWR); + if (n >= 0) { + ioctl(n, TIOCNOTTY, (char *) 0); + (void) close(n); + } + } + /* + * Nuke any timeout value + */ + timeout = NULL; + } + + +#ifdef SYSLOG + /* + * Initialize logging. + */ +#ifndef LOG_CONS +#define LOG_CONS 0 /* Don't bother if not defined... */ +#endif +#ifndef LOG_DAEMON +#define LOG_DAEMON 0 +#endif + openlog("bootpd", LOG_PID | LOG_CONS, LOG_DAEMON); +#endif + + /* + * Log startup + */ + report(LOG_INFO, "%s", Version); + + /* + * Get our timezone offset so we can give it to clients if the + * configuration file doesn't specify one. + */ + if (gettimeofday(&tp, &tzp) < 0) { + secondswest = 0L; /* Assume GMT for lack of anything better */ + report(LOG_ERR, "gettimeofday: %s\n", get_errmsg()); + } else { + secondswest = 60L * tzp.tz_minuteswest; /* Convert to seconds */ + } + + /* + * Allocate hash tables for hardware address, ip address, and hostname + */ + hwhashtable = hash_Init(HASHTABLESIZE); + iphashtable = hash_Init(HASHTABLESIZE); + nmhashtable = hash_Init(HASHTABLESIZE); + if (!(hwhashtable && iphashtable && nmhashtable)) { + report(LOG_ERR, "Unable to allocate hash tables.\n"); + exit(1); + } + + + /* + * Read the bootptab file once immediately upon startup. + */ + readtab(); + + + if (standalone) { + /* + * Create a socket. + */ + if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + report(LOG_ERR, "socket: %s\n", 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 { + report(LOG_ERR, + "udp/bootps: unknown service -- assuming port %d\n", + IPPORT_BOOTPS); + bootps_port = (u_short) IPPORT_BOOTPS; + } + + /* + * Bind socket to BOOTPS port. + */ + s_in.sin_family = AF_INET; + s_in.sin_addr.s_addr = INADDR_ANY; + s_in.sin_port = htons(bootps_port); + if (bind(s, &s_in, sizeof(s_in)) < 0) { + report(LOG_ERR, "bind: %s\n", get_network_errmsg()); + exit(1); + } + } + + + /* + * 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, + "udp/bootpc: unknown service -- assuming port %d\n", + IPPORT_BOOTPC); + bootpc_port = (u_short) IPPORT_BOOTPC; + } + + + /* + * Determine network configuration. + */ + ifconf.ifc_len = sizeof(ifreq); + ifconf.ifc_req = ifreq; + if ((ioctl(s, SIOCGIFCONF, (caddr_t) &ifconf) < 0) || + (ifconf.ifc_len <= 0)) { + report(LOG_ERR, "ioctl: %s\n", get_network_errmsg()); + exit(1); + } + + + /* + * Set up signals to read or dump the table. + */ + if ((int) signal(SIGHUP, readtab) < 0) { + report(LOG_ERR, "signal: %s\n", get_errmsg()); + exit(1); + } +#ifdef DEBUG + if ((int) signal(SIGUSR1, dumptab) < 0) { + report(LOG_ERR, "signal: %s\n", get_errmsg()); + exit(1); + } +#endif + + /* + * Process incoming requests. + */ + for (;;) { + readfds = 1 << s; + nfound = select(s + 1, &readfds, NULL, NULL, timeout); + if (nfound < 0) { + if (errno != EINTR) { + report(LOG_ERR, "select: %s\n", get_errmsg()); + } + continue; + } + if (!(readfds & (1 << s))) { + report(LOG_INFO, "exiting after %ld minutes of inactivity\n", + actualtimeout.tv_sec / 60); + exit(0); + } + fromlen = sizeof(from); + n = recvfrom(s, buf, sizeof(buf), 0, &from, &fromlen); + if (n <= 0) { + continue; + } + + if (n < sizeof(struct bootp)) { + if (debug) { + report(LOG_INFO, "received short packet\n"); + } + continue; + } + + readtab(); /* maybe re-read bootptab */ + switch (bp->bp_op) { + case BOOTREQUEST: + request(); + break; + + case BOOTREPLY: + reply(); + break; + } + } +} + + + + +/* + * Print "usage" message and exit + */ + +void usage() +{ + fprintf(stderr, +"usage: bootpd [-d level] [-i] [-s] [-t timeout] [configfile [dumpfile]]\n"); + fprintf(stderr, "\t -d n\tset debug level\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); +} + + + +/* + * Process BOOTREQUEST packet. + * + * (Note, this version of the bootpd.c server never forwards + * the request to another server. In our environment the + * stand-alone gateways perform that function.) + * + * (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.) + */ +request() +{ + struct bootp *bp = (struct bootp *) buf; + struct host *hp; + int n; + char *path; + struct host dummyhost; + long bootsize; + unsigned hlen, hashcode; + char realpath[1024]; + + /* + * 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((char *)bp->bp_sname, hostname)) { + return; + } + } else { + strcpy((char *)bp->bp_sname, hostname); + } + bp->bp_op = BOOTREPLY; + if (bp->bp_ciaddr.s_addr == 0) { + /* + * client doesnt know his IP address, + * search by hardware address. + */ + if (debug) { + report(LOG_INFO, "request from %s address %s\n", + netname(bp->bp_htype), + haddrtoa(bp->bp_chaddr, bp->bp_htype)); + } + + dummyhost.htype = bp->bp_htype; + hlen = haddrlength(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) { + report(LOG_NOTICE, "%s address not found: %s\n", + netname(bp->bp_htype), + haddrtoa(bp->bp_chaddr, bp->bp_htype)); + return; /* not found */ + } + (bp->bp_yiaddr).s_addr = hp->iaddr.s_addr; + + } else { + + /* + * search by IP address. + */ + if (debug) { + report(LOG_INFO, "request from IP addr %s\n", + inet_ntoa(bp->bp_ciaddr)); + } + dummyhost.iaddr.s_addr = bp->bp_ciaddr.s_addr; + hashcode = hash_HashFunction((char *) &(bp->bp_ciaddr.s_addr), 4); + hp = (struct host *) hash_Lookup(iphashtable, hashcode, iplookcmp, + &dummyhost); + if (hp == NULL) { + report(LOG_NOTICE, + "IP address not found: %s\n", inet_ntoa(bp->bp_ciaddr)); + return; + } + } + + if (debug) { + report(LOG_INFO, "found %s %s\n", inet_ntoa(hp->iaddr), + hp->hostname->string); + } + + /* + * If a specific TFTP server address was specified in the bootptab file, + * fill it in, otherwise zero it. + */ + (bp->bp_siaddr).s_addr = (hp->flags.bootserver) ? + hp->bootserver.s_addr : 0L; + + /* + * This next line is a bit of a mystery. It seems to be vestigial + * code (from Stanford???) which should probably be axed. + */ + if (strcmp(bp->bp_file, "sunboot14") == 0) + bp->bp_file[0] = 0; /* pretend it's null */ + + + /* + * 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 + * 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" + * + */ + + if (hp->flags.tftpdir) { + strcpy(realpath, hp->tftpdir->string); + path = &realpath[strlen(realpath)]; + } else { + path = realpath; + } + + if (bp->bp_file[0]) { + /* + * The client specified a file. + */ + if (bp->bp_file[0] == '/') { + strcpy(path, bp->bp_file); /* Absolute pathname */ + } else { + if (hp->flags.homedir) { + strcpy(path, hp->homedir->string); + strcat(path, "/"); + strcat(path, bp->bp_file); + } else { + report(LOG_NOTICE, + "requested file \"%s\" not found: hd unspecified\n", + bp->bp_file); + return; + } + } + } else { + /* + * No file specified by the client. + */ + if (hp->flags.bootfile && ((hp->bootfile->string)[0] == '/')) { + strcpy(path, hp->bootfile->string); + } else if (hp->flags.homedir && hp->flags.bootfile) { + strcpy(path, hp->homedir->string); + strcat(path, "/"); + strcat(path, hp->bootfile->string); + } else { + bzero(bp->bp_file, sizeof(bp->bp_file)); + goto skip_file; /* Don't bother trying to access the file */ + } + } + + /* + * First try to find the file with a ".host" suffix + */ + n = strlen(path); + strcat(path, "."); + strcat(path, hp->hostname->string); + if (chk_access(realpath, &bootsize) < 0) { + path[n] = 0; /* Try it without the suffix */ + if (chk_access(realpath, &bootsize) < 0) { + if (bp->bp_file[0]) { + /* + * Client wanted specific file + * and we didn't have it. + */ + report(LOG_NOTICE, + "requested file not found: \"%s\"\n", path); + return; + } else { + /* + * 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 skip_file; + } + } + } + strcpy(bp->bp_file, path); + +skip_file: ; + + + + if (debug > 1) { + report(LOG_INFO, "vendor magic field is %d.%d.%d.%d\n", + (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. + */ + if (!hp->flags.vm_auto) { + bcopy(hp->vm_cookie, bp->bp_vend, 4); + } + + /* + * Figure out the format for the vendor-specific info. + */ + if (!bcmp(bp->bp_vend, vm_cmu, 4)) { + /* Not an RFC1048 bootp client */ +#ifdef VEND_CMU + dovend_cmu(bp, hp); +#else + dovend_rfc1048(bp, hp, bootsize); +#endif + } else { + /* RFC1048 conformant bootp client */ + dovend_rfc1048(bp, hp, bootsize); + } + sendreply(0); +} + + + +/* + * Process BOOTREPLY packet (something is using us as a gateway). + */ + +reply() +{ + if (debug) { + report(LOG_INFO, "processing boot reply\n"); + } + sendreply(1); +} + + + +/* + * Send a reply packet to the client. 'forward' flag is set if we are + * not the originator of this reply packet. + */ +sendreply(forward) + int forward; +{ + struct bootp *bp = (struct bootp *) buf; + struct in_addr dst; + struct sockaddr_in to; + + to = s_in; + + to.sin_port = htons(bootpc_port); + /* + * 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 (bp->bp_ciaddr.s_addr) { + dst = bp->bp_ciaddr; + } else if (bp->bp_giaddr.s_addr && forward == 0) { + dst = bp->bp_giaddr; + to.sin_port = htons(bootps_port); + } else { + dst = bp->bp_yiaddr; + setarp(&dst, bp->bp_chaddr, bp->bp_hlen); + } + + if (forward == 0) { + /* + * 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). + */ +#if 0 + int maxmatch = 0; + int len, m; + struct ifreq *ifrq, *ifrmax; + + ifrmax = ifrq = &ifreq[0]; + len = ifconf.ifc_len; + for (; len > 0; len -= sizeof(ifreq[0]), ifrq++) { + m = nmatch(&dst, &((struct sockaddr_in *) + (&ifrq->ifr_addr))->sin_addr); + if (m > maxmatch) { + maxmatch = m; + ifrmax = ifrq; + } + } +#endif + int maxmatch = 0; + int len, m; + struct ifreq *ifrq, *ifrmax; + char *p; + + ifrmax = &ifreq[0]; + p = (char *)&ifreq[0]; + len = ifconf.ifc_len; + for (; len > 0; ) { + ifrq = (struct ifreq *)p; + m = nmatch(&dst, &((struct sockaddr_in *) + (&ifrq->ifr_addr))->sin_addr); + if (m > maxmatch) { + maxmatch = m; + ifrmax = ifrq; + } + p += ifrq->ifr_addr.sa_len + IFNAMSIZ; + len -= ifrq->ifr_addr.sa_len + IFNAMSIZ; + } + + if (bp->bp_giaddr.s_addr == 0) { + if (maxmatch == 0) { + return; + } + bp->bp_giaddr = ((struct sockaddr_in *) + (&ifrmax->ifr_addr))->sin_addr; + } + + /* + * If a specific TFTP server address wasn't specified + * in the bootptab file, fill in our own address. + */ + if (bp->bp_siaddr.s_addr == 0) { + bp->bp_siaddr = ((struct sockaddr_in *) + (&ifrmax->ifr_addr))->sin_addr; + } + } + + to.sin_addr = dst; + if (sendto(s, bp, sizeof(struct bootp), 0, &to, sizeof(to)) < 0) { + report(LOG_ERR, "sendto: %s\n", get_network_errmsg()); + } +} + + + +/* + * Return the number of leading bytes matching in the + * internet addresses supplied. + */ +nmatch(ca,cb) + char *ca, *cb; +{ + int n,m; + + for (m = n = 0 ; n < 4 ; n++) { + if (*ca++ != *cb++) + return(m); + m++; + } + return(m); +} + + + +/* + * Setup the arp cache so that IP address 'ia' will be temporarily + * bound to hardware address 'ha' of length 'len'. + */ +setarp(ia, ha, len) + struct in_addr *ia; + byte *ha; + int len; +{ + struct sockaddr_in *si; + + bzero((caddr_t)&arpreq, sizeof(arpreq)); + + arpreq.arp_pa.sa_family = AF_INET; + si = (struct sockaddr_in *) &arpreq.arp_pa; + si->sin_addr = *ia; + + arpreq.arp_flags = ATF_INUSE | ATF_COM; + + bcopy(ha, arpreq.arp_ha.sa_data, len); + + if (ioctl(s, SIOCSARP, (caddr_t)&arpreq) < 0) { + report(LOG_ERR, "ioctl(SIOCSARP): %s\n", get_network_errmsg()); + } +} + + + +/* + * This call checks read access to a file. It returns 0 if the file given + * by "path" exists and is publically 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. + */ + +int chk_access(path, filesize) +char *path; +long *filesize; +{ + struct stat buf; + + if ((stat(path, &buf) == 0) && (buf.st_mode & (S_IREAD >> 6))) { + *filesize = (long) buf.st_size; + return 0; + } else { + return -1; + } +} + + + +#ifdef DEBUG + +/* + * Dump the internal memory database to bootpd_dump. + */ + +dumptab() +{ + int n; + struct host *hp; + FILE *fp; + long t; + + /* + * Open bootpd.dump file. + */ + if ((fp = fopen(bootpd_dump, "w")) == NULL) { + report(LOG_ERR, "error opening \"%s\": %s\n", bootpd_dump, + get_errmsg()); + exit(1); + } + + t = time(NULL); + fprintf(fp, "\n# %s\n", Version); + fprintf(fp, "# %s: dump of bootp server database.\n", bootpd_dump); + fprintf(fp, "#\n# Dump taken %s", ctime(&t)); + fprintf(fp, "#\n#\n# Legend:\n"); + fprintf(fp, "#\tbf -- bootfile\n"); + fprintf(fp, "#\tbs -- bootfile size in 512-octet blocks\n"); + fprintf(fp, "#\tcs -- cookie servers\n"); + fprintf(fp, "#\tdf -- dump file name\n"); + fprintf(fp, "#\tdn -- domain name\n"); + fprintf(fp, "#\tds -- domain name servers\n"); + fprintf(fp, "#\tgw -- gateways\n"); + fprintf(fp, "#\tha -- hardware address\n"); + fprintf(fp, "#\thd -- home directory for bootfiles\n"); + fprintf(fp, "#\tht -- hardware type\n"); + fprintf(fp, "#\tim -- impress servers\n"); + fprintf(fp, "#\tip -- host IP address\n"); + fprintf(fp, "#\tlg -- log servers\n"); + fprintf(fp, "#\tlp -- LPR servers\n"); + fprintf(fp, "#\tns -- IEN-116 name servers\n"); + fprintf(fp, "#\trl -- resource location protocol servers\n"); + fprintf(fp, "#\trp -- root path\n"); + fprintf(fp, "#\tsm -- subnet mask\n"); + fprintf(fp, "#\tsw -- swap server\n"); + fprintf(fp, "#\tto -- time offset (seconds)\n"); + fprintf(fp, "#\tts -- time servers\n\n\n"); + + n = 0; + for (hp = (struct host *) hash_FirstEntry(nmhashtable); hp != NULL; + hp = (struct host *) hash_NextEntry(nmhashtable)) { + dump_host(fp, hp); + fprintf(fp, "\n"); + n++; + } + fclose(fp); + + report(LOG_INFO, "dumped %d entries to \"%s\".\n", n, bootpd_dump); +} + + + +/* + * Dump all the available information on the host pointed to by "hp". + * The output is sent to the file pointed to by "fp". + */ + +void dump_host(fp, hp) +FILE *fp; +struct host *hp; +{ + int i; + byte *dataptr; + + if (hp) { + if (hp->hostname) { + fprintf(fp, "%s:", hp->hostname->string); + } + if (hp->flags.bootfile) { + fprintf(fp, "bf=%s:", hp->bootfile->string); + } + if (hp->flags.bootsize) { + fprintf(fp, "bs="); + if (hp->flags.bootsize_auto) { + fprintf(fp, "auto:"); + } else { + fprintf(fp, "%d:", hp->bootsize); + } + } + if (hp->flags.cookie_server) { + fprintf(fp, "cs="); + list_ipaddresses(fp, hp->cookie_server); + fprintf(fp, ":"); + } + if (hp->flags.domain_server) { + fprintf(fp, "ds="); + list_ipaddresses(fp, hp->domain_server); + fprintf(fp, ":"); + } + if (hp->flags.gateway) { + fprintf(fp, "gw="); + list_ipaddresses(fp, hp->gateway); + fprintf(fp, ":"); + } + if (hp->flags.homedir) { + fprintf(fp, "hd=%s:", hp->homedir->string); + } + if (hp->flags.name_switch && hp->flags.send_name) { + fprintf(fp, "hn:"); + } + if (hp->flags.htype) { + fprintf(fp, "ht=%u:", (unsigned) hp->htype); + if (hp->flags.haddr) { + fprintf(fp, "ha=%s:", haddrtoa(hp->haddr, hp->htype)); + } + } + if (hp->flags.impress_server) { + fprintf(fp, "im="); + list_ipaddresses(fp, hp->impress_server); + fprintf(fp, ":"); + } + if (hp->flags.swap_server) { + fprintf(fp, "sw=%s:", inet_ntoa(hp->subnet_mask)); + } + if (hp->flags.iaddr) { + fprintf(fp, "ip=%s:", inet_ntoa(hp->iaddr)); + } + if (hp->flags.log_server) { + fprintf(fp, "lg="); + list_ipaddresses(fp, hp->log_server); + fprintf(fp, ":"); + } + if (hp->flags.lpr_server) { + fprintf(fp, "lp="); + list_ipaddresses(fp, hp->lpr_server); + fprintf(fp, ":"); + } + if (hp->flags.name_server) { + fprintf(fp, "ns="); + list_ipaddresses(fp, hp->name_server); + fprintf(fp, ":"); + } + if (hp->flags.rlp_server) { + fprintf(fp, "rl="); + list_ipaddresses(fp, hp->rlp_server); + fprintf(fp, ":"); + } + if (hp->flags.bootserver) { + fprintf(fp, "sa=%s:", inet_ntoa(hp->bootserver)); + } + if (hp->flags.subnet_mask) { + fprintf(fp, "sm=%s:", inet_ntoa(hp->subnet_mask)); + } + if (hp->flags.tftpdir) { + fprintf(fp, "td=%s:", hp->tftpdir->string); + } + if (hp->flags.rootpath) { + fprintf(fp, "rp=%s:", hp->rootpath->string); + } + if (hp->flags.domainname) { + fprintf(fp, "dn=%s:", hp->domainname->string); + } + if (hp->flags.dumpfile) { + fprintf(fp, "df=%s:", hp->dumpfile->string); + } + if (hp->flags.time_offset) { + if (hp->flags.timeoff_auto) { + fprintf(fp, "to=auto:"); + } else { + fprintf(fp, "to=%ld:", hp->time_offset); + } + } + if (hp->flags.time_server) { + fprintf(fp, "ts="); + list_ipaddresses(fp, hp->time_server); + fprintf(fp, ":"); + } + if (hp->flags.vendor_magic) { + fprintf(fp, "vm="); + if (hp->flags.vm_auto) { + fprintf(fp, "auto:"); + } else if (!bcmp(hp->vm_cookie, vm_cmu, 4)) { + fprintf(fp, "cmu:"); + } else if (!bcmp(hp->vm_cookie, vm_rfc1048, 4)) { + fprintf(fp, "rfc1048"); + } else { + fprintf(fp, "%d.%d.%d.%d:", + (int) ((hp->vm_cookie)[0]), + (int) ((hp->vm_cookie)[1]), + (int) ((hp->vm_cookie)[2]), + (int) ((hp->vm_cookie)[3])); + } + } + if (hp->flags.generic) { + fprintf(fp, "generic="); + dataptr = hp->generic->data; + for (i = hp->generic->length; i > 0; i--) { + fprintf(fp, "%02X", (int) *dataptr++); + } + fprintf(fp, ":"); + } + } +} + + + +/* + * Dump an entire struct in_addr_list of IP addresses to the indicated file. + * + * The addresses are printed in standard ASCII "dot" notation and separated + * from one another by a single space. A single leading space is also + * printed before the first adddress. + * + * Null lists produce no output (and no error). + */ + +void list_ipaddresses(fp, ipptr) + FILE *fp; + struct in_addr_list *ipptr; +{ + unsigned count; + struct in_addr *addrptr; + + if (ipptr) { + count = ipptr->addrcount; + addrptr = ipptr->addr; + if (count-- > 0) { + fprintf(fp, "%s", inet_ntoa(*addrptr++)); + while (count-- > 0) { + fprintf(fp, " %s", inet_ntoa(*addrptr++)); + } + } + } +} +#endif /* DEBUG */ + + + +#ifdef VEND_CMU + +/* + * Insert the CMU "vendor" data for the host pointed to by "hp" into the + * bootp packet pointed to by "bp". + */ + +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; + 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; + } + } + } + strcpy(vendp->v_magic, vm_cmu); + + if (debug > 1) { + report(LOG_INFO, "sending CMU-style reply\n"); + } +} + +#endif /* VEND_CMU */ + + + +/* + * Insert the RFC1048 vendor data for the host pointed to by "hp" into the + * bootp packet pointed to by "bp". + */ + +void dovend_rfc1048(bp, hp, bootsize) + struct bootp *bp; + struct host *hp; + long bootsize; +{ + int bytesleft, len; + byte *vp; + char *tmpstr; + + vp = bp->bp_vend; + bytesleft = sizeof(bp->bp_vend); /* Initial vendor area size */ + bcopy(vm_rfc1048, vp, 4); /* Copy in the magic cookie */ + vp += 4; + bytesleft -= 4; + + if (hp->flags.time_offset) { + *vp++ = TAG_TIME_OFFSET; /* -1 byte */ + *vp++ = 4; /* -1 byte */ + if (hp->flags.timeoff_auto) { + insert_u_long(htonl(secondswest), &vp); + } else { + insert_u_long(htonl(hp->time_offset), &vp); /* -4 bytes */ + } + bytesleft -= 6; + } + if (hp->flags.subnet_mask) { + *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) { + insert_ip(TAG_GATEWAY, hp->gateway, &vp, &bytesleft); + } + } + if (hp->flags.bootsize) { + bootsize = (hp->flags.bootsize_auto) ? + ((bootsize + 511) / 512) : (hp->bootsize); /* Round up */ + *vp++ = TAG_BOOTSIZE; + *vp++ = 2; + *vp++ = (byte) ((bootsize >> 8) & 0xFF); + *vp++ = (byte) (bootsize & 0xFF); + bytesleft -= 4; /* Tag, length, and 16 bit blocksize */ + } + if (hp->flags.swap_server) { + *vp++ = TAG_SWAPSERVER; /* -1 byte */ + *vp++ = 4; /* -1 byte */ + insert_u_long(hp->swapserver.s_addr, &vp); /* -4 bytes */ + bytesleft -= 6; /* Fix real count */ + } + if (hp->flags.rootpath) { + /* + * Check for room for rootpath. Add 2 to account for + * TAG_ROOTPATH and length. + */ + len = strlen(hp->rootpath->string); + if ((len + 2) <= bytesleft) { + *vp++ = TAG_ROOTPATH; + *vp++ = (byte) (len & 0xFF); + bcopy(hp->rootpath->string, vp, len); + vp += len; + bytesleft -= len + 2; + } + } + if (hp->flags.dumpfile) { + /* + * Check for room for dumpfile. Add 2 to account for + * TAG_DUMPFILE and length. + */ + len = strlen(hp->dumpfile->string); + if ((len + 2) <= bytesleft) { + *vp++ = TAG_DUMPFILE; + *vp++ = (byte) (len & 0xFF); + bcopy(hp->dumpfile->string, vp, len); + vp += len; + bytesleft -= len + 2; + } + } + if (hp->flags.domain_server) { + insert_ip(TAG_DOMAIN_SERVER, hp->domain_server, &vp, &bytesleft); + } + if (hp->flags.domainname) { + /* + * Check for room for domainname. Add 2 to account for + * TAG_DOMAINNAME and length. + */ + len = strlen(hp->domainname->string); + if ((len + 2) <= bytesleft) { + *vp++ = TAG_DOMAINNAME; + *vp++ = (byte) (len & 0xFF); + bcopy(hp->domainname->string, vp, len); + vp += len; + bytesleft -= len + 2; + } + } + if (hp->flags.name_server) { + insert_ip(TAG_NAME_SERVER, hp->name_server, &vp, &bytesleft); + } + if (hp->flags.rlp_server) { + insert_ip(TAG_RLP_SERVER, hp->rlp_server, &vp, &bytesleft); + } + if (hp->flags.time_server) { + insert_ip(TAG_TIME_SERVER, hp->time_server, &vp, &bytesleft); + } + if (hp->flags.lpr_server) { + insert_ip(TAG_LPR_SERVER, hp->lpr_server, &vp, &bytesleft); + } + if (hp->flags.name_switch && hp->flags.send_name) { + /* + * Check for room for hostname. Add 2 to account for + * TAG_HOSTNAME and length. + */ + len = strlen(hp->hostname->string); + if ((len + 2) > bytesleft) { + /* + * Not enough room for full (domain-qualified) hostname, try + * stripping it down to just the first field (host). + */ + tmpstr = hp->hostname->string; + len = 0; + while (*tmpstr && (*tmpstr != '.')) { + tmpstr++; + len++; + } + } + if ((len + 2) <= bytesleft) { + *vp++ = TAG_HOSTNAME; + *vp++ = (byte) (len & 0xFF); + bcopy(hp->hostname->string, vp, len); + vp += len; + bytesleft -= len + 2; + } + } + if (hp->flags.cookie_server) { + insert_ip(TAG_COOKIE_SERVER, hp->cookie_server, &vp, &bytesleft); + } + if (hp->flags.log_server) { + insert_ip(TAG_LOG_SERVER, hp->log_server, &vp, &bytesleft); + } + + if (hp->flags.generic) { + insert_generic(hp->generic, &vp, &bytesleft); + } + + if (bytesleft >= 1) { + *vp++ = TAG_END; + bytesleft--; + } + + if (debug > 1) { + report(LOG_INFO, "sending RFC1048-style reply\n"); + } + + if (bytesleft > 0) { + /* + * Zero out any remaining part of the vendor area. + */ + bzero(vp, bytesleft); + } +} + + + +/* + * Compare function to determine whether two hardware addresses are + * equivalent. Returns TRUE if "host1" and "host2" are equivalent, FALSE + * otherwise. + * + * This function is used when retrieving elements from the hardware address + * hash table. + */ + +boolean hwlookcmp(host1, host2) + struct host *host1, *host2; +{ + if (host1->htype != host2->htype) { + return FALSE; + } + if (bcmp(host1->haddr, host2->haddr, haddrlength(host1->htype))) { + return FALSE; + } + return TRUE; +} + + + + +/* + * Compare function for doing IP address hash table lookup. + */ + +boolean iplookcmp(host1, host2) + struct host *host1, *host2; +{ + return (host1->iaddr.s_addr == host2->iaddr.s_addr); +} + + + +/* + * Insert a tag value, a length value, and a list of IP addresses into the + * memory buffer indirectly pointed to by "dest". "tag" is the RFC1048 tag + * number to use, "iplist" is a pointer to a list of IP addresses + * (struct in_addr_list), and "bytesleft" points to an integer which + * indicates the size of the "dest" buffer. The number of IP addresses + * actually inserted + * + * This is used to fill the vendor-specific area of a bootp packet in + * conformance to RFC1048. + */ + +void insert_ip(tag, iplist, dest, bytesleft) + byte tag; + struct in_addr_list *iplist; + byte **dest; + int *bytesleft; +{ + struct in_addr *addrptr; + unsigned addrcount; + byte *d; + + if (iplist && (*bytesleft >= 6)) { + d = *dest; /* Save pointer for later */ + **dest = tag; + (*dest) += 2; + (*bytesleft) -= 2; /* Account for tag and length */ + addrptr = iplist->addr; + addrcount = iplist->addrcount; + while ((*bytesleft >= 4) && (addrcount > 0)) { + insert_u_long(addrptr->s_addr, dest); + addrptr++; + addrcount--; + (*bytesleft) -= 4; /* Four bytes per address */ + } + d[1] = (byte) ((*dest - d - 2) & 0xFF); + } +} + + + +/* + * Insert generic data into a bootp packet. The data is assumed to already + * be in RFC1048 format. It is inserted using a first-fit algorithm which + * attempts to insert as many tags as possible. Tags and data which are + * too large to fit are skipped; any remaining tags are tried until they + * have all been exhausted. + */ + +void insert_generic(gendata, buff, bytesleft) + struct shared_bindata *gendata; + byte **buff; + int *bytesleft; +{ + byte *srcptr; + int length, numbytes; + + if (gendata) { + srcptr = gendata->data; + length = gendata->length; + while ((length > 0) && (*bytesleft > 0)) { + switch (*srcptr) { + case TAG_END: + length = 0; /* Force an exit on next iteration */ + break; + case TAG_PAD: + *(*buff)++ = *srcptr++; + (*bytesleft)--; + length--; + break; + default: + numbytes = srcptr[1] + 2; + if (*bytesleft >= numbytes) { + bcopy(srcptr, *buff, numbytes); + (*buff) += numbytes; + (*bytesleft) -= numbytes; + } + srcptr += numbytes; + length -= numbytes; + break; + } + } + } +} + + + + +/* + * Convert a hardware address to an ASCII string. + */ + +char *haddrtoa(haddr, htype) + byte *haddr; + byte htype; +{ + static char haddrbuf[2 * MAXHADDRLEN + 1]; + char *bufptr; + unsigned count; + + bufptr = haddrbuf; + for (count = haddrlength(htype); count > 0; count--) { + sprintf(bufptr, "%02X", (unsigned) (*haddr++ & 0xFF)); + bufptr += 2; + } + return (haddrbuf); +} + + + +/* + * Insert the unsigned long "value" into memory starting at the byte + * pointed to by the byte pointer (*dest). (*dest) is updated to + * point to the next available byte. + * + * Since it is desirable to internally store network addresses in network + * byte order (in struct in_addr's), this routine expects longs to be + * passed in network byte order. + * + * However, due to the nature of the main algorithm, the long must be in + * host byte order, thus necessitating the use of ntohl() first. + */ + +void insert_u_long(value, dest) + unsigned long value; + byte **dest; +{ + byte *temp; + int n; + + value = ntohl(value); /* Must use host byte order here */ + temp = (*dest += 4); + for (n = 4; n > 0; n--) { + *--temp = (byte) (value & 0xFF); + value >>= 8; + } + /* Final result is network byte order */ +} + + + +/* + * Return pointer to static string which gives full filesystem error message. + */ + +char *get_errmsg() +{ + static char errmsg[80]; + + if (errno < sys_nerr) { + return sys_errlist[errno]; + } else { + sprintf(errmsg, "Error %d", errno); + return errmsg; + } +} + + + +/* + * This routine reports errors and such via stderr and syslog() if + * appopriate. It just helps avoid a lot of "#ifdef SYSLOG" constructs + * from being scattered throughout the code. + * + * The syntax is identical to syslog(3), but %m is not considered special + * for output to stderr (i.e. you'll see "%m" in the output. . .). Also, + * control strings should normally end with \n since newlines aren't + * automatically generated for stderr output (whereas syslog strips out all + * newlines and adds its own at the end). + */ + +/*VARARGS2*/ +void report(priority, fmt, p0, p1, p2, p3, p4) + int priority; + char *fmt; +{ +#ifdef LOG_SALERT + static char *levelnames[] = { + "unknown level: ", + "alert(1): ", + "subalert(2): ", + "emergency(3): ", + "error(4): ", + "critical(5): ", + "warning(6): ", + "notice(7): ", + "information(8):", + "debug(9): ", + "unknown level: " + }; +#else + static char *levelnames[] = { + "emergency(0): ", + "alert(1): ", + "critical(2): ", + "error(3): ", + "warning(4): ", + "notice(5): ", + "information(6):", + "debug(7): ", + "unknown level: " + }; +#endif + + if ((priority < 0) || (priority >= sizeof(levelnames)/sizeof(char *))) { + priority = sizeof(levelnames) / sizeof(char *) - 1; + } + + /* + * Print the message + */ + if (debug > 2) { + fprintf(stderr, "bootpd: %s ", levelnames[priority]); + fprintf(stderr, fmt, p0, p1, p2, p3, p4); + } +#ifdef SYSLOG + syslog(priority, fmt, p0, p1, p2, p3, p4); +#endif +} |
