3809ade385
Obtained from:NetBSD
1381 lines
33 KiB
C
1381 lines
33 KiB
C
/************************************************************************
|
||
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.
|
||
************************************************************************/
|
||
|
||
#ifndef lint
|
||
static char rcsid[] = "$Id: bootpd.c,v 1.4 1994/08/24 18:14:44 gwr 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 RFC 1395 for option tags 14-17.
|
||
* See accompanying man page -- bootpd.8
|
||
*
|
||
* HISTORY
|
||
* See ./Changes
|
||
*
|
||
* BUGS
|
||
* See ./ToDo
|
||
*/
|
||
|
||
|
||
|
||
#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>
|
||
#include <netinet/in.h>
|
||
#include <arpa/inet.h> /* inet_ntoa */
|
||
|
||
#ifndef NO_UNISTD
|
||
#include <unistd.h>
|
||
#endif
|
||
#include <stdlib.h>
|
||
#include <signal.h>
|
||
#include <stdio.h>
|
||
#include <string.h>
|
||
#include <errno.h>
|
||
#include <ctype.h>
|
||
#include <netdb.h>
|
||
#include <syslog.h>
|
||
#include <assert.h>
|
||
|
||
#ifdef NO_SETSID
|
||
# include <fcntl.h> /* for O_RDONLY, etc */
|
||
#endif
|
||
|
||
#ifdef SVR4
|
||
/* Using sigset() avoids the need to re-arm each time. */
|
||
#define signal sigset
|
||
#endif
|
||
|
||
#ifndef USE_BFUNCS
|
||
# include <memory.h>
|
||
/* 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
|
||
*/
|
||
|
||
#ifdef __STDC__
|
||
#define P(args) args
|
||
#else
|
||
#define P(args) ()
|
||
#endif
|
||
|
||
extern void dumptab P((char *));
|
||
|
||
PRIVATE void catcher P((int));
|
||
PRIVATE int chk_access P((char *, int32 *));
|
||
#ifdef VEND_CMU
|
||
PRIVATE void dovend_cmu P((struct bootp *, struct host *));
|
||
#endif
|
||
PRIVATE void dovend_rfc1048 P((struct bootp *, struct host *, int32));
|
||
PRIVATE void handle_reply P((void));
|
||
PRIVATE void handle_request P((void));
|
||
PRIVATE void sendreply P((int forward, int32 dest_override));
|
||
PRIVATE void usage P((void));
|
||
|
||
#undef P
|
||
|
||
/*
|
||
* 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 */
|
||
};
|
||
|
||
/*
|
||
* General
|
||
*/
|
||
|
||
int s; /* Socket file descriptor */
|
||
char *pktbuf; /* Receive packet buffer */
|
||
int pktlen;
|
||
char *progname;
|
||
char *chdir_path;
|
||
char hostname[MAXHOSTNAMELEN]; /* System host name */
|
||
struct in_addr my_ip_addr;
|
||
|
||
/* 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.
|
||
*/
|
||
|
||
void
|
||
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_ERR, "getsockname: not an INET socket");
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Set defaults that might be changed by option switches.
|
||
*/
|
||
stmp = NULL;
|
||
timeout = &actualtimeout;
|
||
|
||
/*
|
||
* Read switches.
|
||
*/
|
||
for (argc--, argv++; argc > 0; argc--, argv++) {
|
||
if (argv[0][0] != '-')
|
||
break;
|
||
switch (argv[0][1]) {
|
||
|
||
case 'c': /* chdir_path */
|
||
if (argv[0][2]) {
|
||
stmp = &(argv[0][2]);
|
||
} else {
|
||
argc--;
|
||
argv++;
|
||
stmp = argv[0];
|
||
}
|
||
if (!stmp || (stmp[0] != '/')) {
|
||
fprintf(stderr,
|
||
"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)) {
|
||
fprintf(stderr,
|
||
"%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) {
|
||
fprintf(stderr,
|
||
"bootpd: missing hostname\n");
|
||
break;
|
||
}
|
||
strncpy(hostname, stmp, sizeof(hostname)-1);
|
||
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)) {
|
||
fprintf(stderr,
|
||
"%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:
|
||
fprintf(stderr, "%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.
|
||
*/
|
||
if (hostname[0] == '\0') {
|
||
if (gethostname(hostname, sizeof(hostname)) == -1) {
|
||
fprintf(stderr, "bootpd: can't get hostname\n");
|
||
exit(1);
|
||
}
|
||
}
|
||
hep = gethostbyname(hostname);
|
||
if (!hep) {
|
||
fprintf(stderr, "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("/dev/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,
|
||
"udp/bootps: unknown service -- assuming 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,
|
||
"udp/bootpc: unknown service -- assuming port %d",
|
||
IPPORT_BOOTPC);
|
||
bootpc_port = (u_short) IPPORT_BOOTPC;
|
||
}
|
||
|
||
/*
|
||
* Set up signals to read or dump the table.
|
||
*/
|
||
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);
|
||
}
|
||
|
||
/*
|
||
* Process incoming requests.
|
||
*/
|
||
for (;;) {
|
||
readfds = 1 << s;
|
||
nfound = select(s + 1, (fd_set *)&readfds, NULL, NULL, timeout);
|
||
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 (!(readfds & (1 << s))) {
|
||
if (debug > 1)
|
||
report(LOG_INFO, "exiting after %ld minutes of inactivity",
|
||
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_INFO, "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;
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
/*
|
||
* Print "usage" message and exit
|
||
*/
|
||
|
||
PRIVATE void
|
||
usage()
|
||
{
|
||
fprintf(stderr,
|
||
"usage: bootpd [-d level] [-i] [-s] [-t timeout] [configfile [dumpfile]]\n");
|
||
fprintf(stderr, "\t -c n\tset current directory\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);
|
||
}
|
||
|
||
/* Signal catchers */
|
||
PRIVATE void
|
||
catcher(sig)
|
||
int sig;
|
||
{
|
||
if (sig == SIGHUP)
|
||
do_readtab = 1;
|
||
if (sig == SIGUSR1)
|
||
do_dumptab = 1;
|
||
#ifdef SYSV
|
||
/* For older "System V" derivatives with no sigset(). */
|
||
/* XXX - Should just do it the POSIX way (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;
|
||
|
||
/* 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 doesnt 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 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 > 1)
|
||
report(LOG_INFO, "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 > 1) {
|
||
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 <edmund@york.ac.uk>
|
||
*
|
||
* 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) {
|
||
strcpy(realpath, 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);
|
||
|
||
null_file_name:
|
||
|
||
|
||
/*
|
||
* 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;
|
||
|
||
/*
|
||
* 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 compatiblity)
|
||
* 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;
|
||
|
||
if (debug > 1)
|
||
report(LOG_INFO, "setarp %s - %s",
|
||
inet_ntoa(dst), haddrtoa(ha, len));
|
||
setarp(s, &dst, 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 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.
|
||
*/
|
||
|
||
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;
|
||
char *tmpstr;
|
||
|
||
static 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)) {
|
||
if (debug > 1)
|
||
report(LOG_INFO, "request has DHCP msglen=%d", msgsz);
|
||
pktlen = msgsz;
|
||
}
|
||
}
|
||
}
|
||
|
||
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:
|
||
*/
|