NetBSD/libexec/bootpd/bootpd.c

1734 lines
41 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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: /cvsroot/src/libexec/bootpd/Attic/bootpd.c,v 1.1 1994/04/18 05:15:54 glass 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[];
#ifndef __NetBSD__
extern char *sys_errlist[];
#endif
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, (struct sockaddr *) &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, (struct sockaddr *) &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, (struct sockaddr *) &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, (struct sockaddr *) &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
}