1
0
Files
irix-657m-src/eoe/cmd/dhcp_server/relay_bootp.c
2022-09-29 17:59:04 +03:00

800 lines
20 KiB
C

/* bootp_relay.c - these functions are borrowed from the dhcp_server.c file
* and here only for handling the bootp requests
*/
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <errno.h>
#include <ctype.h>
#include <netdb.h>
#include <setjmp.h>
#include <syslog.h>
#include <net/raw.h>
#include <netinet/if_ether.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <sys/time.h>
#include <rpc/rpc.h>
#include <rpcsvc/ypclnt.h>
#include "dhcp.h"
#include "dhcpdefs.h"
/* other externs in relay_funcs.c */
extern int s; /* primary input socket */
extern iaddr_t myhostaddr; /* save (main) internet address of executing host */
extern struct ifreq ifreq[]; /* holds interface configuration */
char reg_netmask[30], *reg_net;
char reg_hostnet[30];
#define SINPTR(p) ((struct sockaddr_in *)(p))
/* flags */
extern int debug_f;
extern int forwarding_f;
extern struct netaddr nets[];
/*
* Globals below are associated with the bootp database file (bootptab).
*/
char *bootptab = "/etc/bootptab";
FILE *fp;
char line[256]; /* line buffer for reading bootptab */
char *linep; /* pointer to 'line' */
int linenum; /* current line number in bootptab */
char homedir[64]; /* bootfile homedirectory */
char defaultboot[64]; /* default file to boot */
#define MHOSTS 512 /* max number of 'hosts' structs */
struct hosts {
char host[MAXHOSTNAMELEN+1]; /* host name (and suffix) */
u_char htype; /* hardware type */
u_char haddr[6]; /* hardware address */
iaddr_t iaddr; /* internet address */
char bootfile[32]; /* default boot file name */
} hosts[MHOSTS];
int nhosts; /* current number of hosts */
long modtime; /* last modification time of bootptab */
int max_addr_alloc = 64;
int numaddrs;
u_int *myaddrs; /* save addresses of executing host */
char myname[MAXHOSTNAMELEN+1]; /* my primary name */
void forward(struct bootp *, iaddr_t *, iaddr_t *);
void readtab(void);
void getfield(char *, int);
void setarp(iaddr_t *, u_char *, int);
void makenamelist(void);
extern void sendreply(struct bootp *, int, int, int, int);
extern int ether_ntohost(char *, struct ether_addr *);
extern int registerinethost(char *, char *, char *, struct in_addr *, char *);
extern FILE *log_fopen(char *, char *);
char *
iaddr_ntoa(iaddr_t addr)
{
struct hostent *hp;
if (hp = gethostbyaddr(&addr, sizeof(addr), AF_INET)) {
return hp->h_name;
} else {
return inet_ntoa(addr);
}
}
/*
* Perform Reverse ARP lookup using /etc/ether and /etc/hosts (or
* their NIS equivalents)
*
* Returns 1 on success, 0 on failure.
*/
int
reverse_arp(struct ether_addr *eap, iaddr_t *iap)
{
register struct hostent *hp;
char host[512];
/*
* Call routine to access /etc/ethers or its NIS equivalent
*/
if (ether_ntohost(host, eap))
return (0);
/*
* Now access the hostname database.
*/
if ((hp = gethostbyname(host)) == 0) {
syslog(LOG_ERR, "gethostbyname(%s) failed: %s", host, hstrerror(h_errno));
return (0);
}
/*
* Return primary Internet address
*/
iap->s_addr = *(u_long *)(hp->h_addr_list[0]);
return (1);
}
/*
* Check the passed name against the current host's addresses.
*
* Return value
* TRUE if match
* FALSE if no match
*/
int
matchhost(char *name)
{
register struct hostent *hp;
int i;
u_int requested_addr;
if (hp = gethostbyname(name)) {
requested_addr = *(u_long *)(hp->h_addr_list[0]);
for (i = 0; i < numaddrs; i++)
if (requested_addr == myaddrs[i])
return 1;
}
return 0;
}
/*
* Check whether two passed IP addresses are on the same wire.
* The first argument is the IP address of the requestor, so
* it must be on one of the wires to which the server is
* attached.
*
* Return value
* TRUE if on same wire
* FALSE if not on same wire
*/
int
samewire(register iaddr_t *src, register iaddr_t *dest)
{
register struct netaddr *np;
/*
* It may well happen that src is zero, since the datagram
* comes from a PROM that may not yet know its own IP address.
* In that case the socket layer doesnt know what to fill in
* as the from address. In that case, we have to assume that
* the source and dest are on different wires. This means
* we will be doing forwarding sometimes when it isnt really
* necessary.
*/
if (src->s_addr == 0 || dest->s_addr == 0)
return 0;
/*
* In order to take subnetworking into account, one must
* use the netmask to tell how much of the IP address
* actually corresponds to the real network number.
*
* Search the table of nets to which the server is connected
* for the net containing the source address.
*/
for (np = nets; np->netmask != 0; np++)
if ((src->s_addr & np->netmask) == np->net)
return ((dest->s_addr & np->netmask) == np->net);
syslog(LOG_ERR, "can't find source net for %s", inet_ntoa(*src));
return 0;
}
char hostmap[] = "hosts.byname";
/*
* A new diskless workstation/autoreg is asking for
* initial boot/IPADDR
*/
int
handle_diskless(struct bootp *rq, struct bootp *rp)
{
char *t_host, *domain, *ypmaster, *str_match, *t_domain;
char hostname[256], alias[256];
int err = 0;
if (yp_get_default_domain(&domain)) {
syslog(LOG_ERR, "Autoreg: can't get domain");
return(-1);
}
t_host = (char *)rq->vd_clntname;
if (debug_f)
syslog(LOG_DEBUG, "Autoreg: domain=%s, host=%s", domain, t_host);
gethostname(hostname, sizeof(hostname));
if (gethostbyname(t_host) != NULL) {
/* play safe */
rp->vd_flags = VF_RET_IPADDR;
return(0);
}
if (!reg_netmask) {
if (str_match = strrchr(reg_hostnet, '.'))
*str_match = 0;
}
/* get ypmaster ipaddr */
if (yp_master(domain, hostmap, &ypmaster)) {
if (debug_f)
syslog(LOG_DEBUG, "no \"%s\" YPmaster found for \"%s\" domain",
hostmap, domain);
return(-1);
}
/* check and prevent broadcast storm */
if (!matchhost(ypmaster)) {
if (!forwarding_f) {
if (debug_f)
syslog(LOG_DEBUG, "Autoreg: I am neither YPMASTER nor GATEWAY");
return(-1);
}
if (debug_f)
syslog(LOG_DEBUG, "Autoreg: I am GATEWAY");
/* XXX
* finding out if the requestor and YPMASTER are
* at same sub-net is impossible. So let's
* allow gateway issue an extra broadcast msg.
*/
} else if (debug_f)
syslog(LOG_DEBUG, "Autoreg: I am YPMASTER");
/* check/create full host name */
if ((t_domain = strchr((const char *)rq->vd_clntname, '.')) != NULL) {
/* if it not for the domain i belong, then ignore */
if (strcmp(domain, t_domain+1)) {
if (debug_f)
syslog( LOG_DEBUG, "Autoreg: req_domain(%s)<>domain(%s)",
t_domain+1, domain);
return(-1);
}
strcpy(hostname, t_host);
*t_domain = 0;
strcpy(alias, t_host);
*t_domain = '.';
} else {
strcpy(hostname, t_host);
strcat(hostname, ".");
strcat(hostname, domain);
strcpy(alias, t_host);
}
/* can't create, other bootp may be creating at the same time
or, there is no NIS */
err = registerinethost(hostname,reg_hostnet,reg_netmask,0,alias);
if ((u_long)err <= YPERR_BUSY) {
syslog(LOG_ERR, "Autoreg: REGISTER failed(0x%x)", err);
return(-1);
}
rp->vd_flags = VF_NEW_IPADDR;
rp->bp_yiaddr.s_addr = (u_long)(err);
if (debug_f)
syslog( LOG_DEBUG, "Autoreg: %s REGISTERED as %s",
hostname, inet_ntoa(*(struct in_addr *)&err));
return(0);
}
/* process_bootp_message - handle regular bootp messages
*/
void
process_bootp_message(struct bootp *rq)
{
struct bootp rp;
char path[MAXPATHLEN], file[128];
iaddr_t fromaddr;
register struct hosts *hp;
register struct hostent *hostentp;
register n;
int has_sname; /* does rq have nonempty bp_sname? */
struct ifreq ifnetmask;
rp = *rq; /* copy request into reply */
rp.bp_op = BOOTREPLY;
readtab(); /* (re)read bootptab */
strncpy(ifnetmask.ifr_name, ifreq[0].ifr_name,
sizeof(ifnetmask.ifr_name));
if (ioctl(s, SIOCGIFNETMASK, (caddr_t)&ifnetmask) < 0) {
syslog(LOG_ERR, "netmask ioctl failed (%m)");
return;
}
reg_net = inet_ntoa(SINPTR(&ifnetmask.ifr_addr)->sin_addr);
strcpy(reg_netmask, reg_net);
if (ioctl(s, SIOCGIFADDR, (caddr_t)&ifnetmask) < 0) {
syslog(LOG_ERR, "netaddr ioctl failed (%m)");
return;
}
reg_net = inet_ntoa(SINPTR(&ifnetmask.ifr_addr)->sin_addr);
strcpy(reg_hostnet, reg_net);
/* This is a non dhcp, regular bootp packet */
/*
* Let's resolve client's ipaddr first.
*/
if (rq->bp_yiaddr.s_addr != 0 && rq->bp_giaddr.s_addr != 0) {
/*
* yiaddr has already been filled in by forwarding bootp
*/
hp = (struct hosts *) 0;
} else if (rq->bp_ciaddr.s_addr == 0) {
/*
* client doesnt know his IP address,
* search by hardware address.
*/
for (hp = &hosts[0], n = 0 ; n < nhosts ; n++,hp++)
if (rq->bp_htype == hp->htype &&
bcmp(rq->bp_chaddr, hp->haddr, 6) == 0)
break;
if (n == nhosts) {
/*
* The requestor isn't listed in bootptab.
*/
hp = (struct hosts *) 0;
if( (rp.vd_magic == VM_SGI) && (rp.vd_flags == VF_GET_IPADDR) ) {
if ((hostentp =
gethostbyname((const char *)rp.vd_clntname)) == 0) {
rp.bp_yiaddr.s_addr = 0;
} else {
rp.bp_yiaddr.s_addr= *(u_long *)(hostentp->h_addr_list[0]);
rp.vd_flags = VF_RET_IPADDR;
}
}
/*
* Try Reverse ARP using /etc/ethers or NIS before
* giving up.
*/
if (!reverse_arp((struct ether_addr *)rq->bp_chaddr, &rp.bp_yiaddr)) {
/*
* Don't trash the request at this point,
* since it may be for another server with
* better tables than we have.
*/
if (debug_f)
syslog(LOG_DEBUG, "no Internet address for %s",
ether_ntoa((struct ether_addr *)rq->bp_chaddr));
/* Play it safe */
rp.bp_yiaddr.s_addr = 0;
}
} else {
rp.bp_yiaddr = hp->iaddr;
}
} else {
/* search by IP address */
for (hp = &hosts[0], n = 0 ; n < nhosts ; n++,hp++)
if (rq->bp_ciaddr.s_addr == hp->iaddr.s_addr)
break;
if (n == nhosts) {
/*
* The requestor knows his Internet address already,
* but he isn't listed in bootptab. Try to satisfy
* the request anyway.
*/
hp = (struct hosts *) 0;
}
}
fromaddr = rq->bp_ciaddr.s_addr ? rq->bp_ciaddr : rp.bp_yiaddr;
/*
* Check whether the requestor specified a particular server.
* If not, fill in the name of the current host. If a
* particular server was requested, then don't answer the
* request unless the name matches our name or one of our
* aliases.
*/
has_sname = rq->bp_sname[0] != '\0';
if (!has_sname)
strncpy((char *)rp.bp_sname, myname, sizeof(rp.bp_sname));
else if (!matchhost((char *) rq->bp_sname)) {
iaddr_t destaddr;
if (debug_f)
syslog(LOG_DEBUG, "%s: request for server %s",
iaddr_ntoa(fromaddr), rq->bp_sname);
/*
* Not for us.
*/
if (!forwarding_f)
return;
/*
* Look up the host by name and decide whether
* we should forward the message to him.
*/
if ((hostentp = gethostbyname((const char *)rq->bp_sname)) == 0) {
syslog(LOG_INFO, "%s: request for unknown server %s",
iaddr_ntoa(fromaddr), rq->bp_sname);
return;
}
destaddr.s_addr = *(u_long *)(hostentp->h_addr_list[0]);
/*
* If the other server is on a different cable from the
* requestor, then forward the request. If on the same
* wire, there is no point in forwarding. Note that in
* the case that we don't yet know the IP address of the
* client, there is no way to tell whether the client and
* server are actually on the same wire. In that case
* we forward regardless. It's redundant, but there's
* no way to get around it.
*/
if (!samewire(&fromaddr, &destaddr)) {
/*
* If we were able to compute the client's Internet
* address, pass that information along in case the
* other server doesn't have the info.
*/
rq->bp_yiaddr = rp.bp_yiaddr;
forward(rq, &fromaddr, &destaddr);
}
return;
}
if ( (fromaddr.s_addr == 0) && (rq->vd_magic == VM_AUTOREG) &&
rq->vd_clntname && rq->vd_clntname[0] ) {
if (debug_f)
syslog(LOG_DEBUG, "Autoreg starts");
if (handle_diskless(rq, &rp))
return;
fromaddr = rp.bp_yiaddr;
}
/*
* If we get here and the 'from' address is still zero, that
* means we don't recognize the client and we can't pass the buck.
* So we have to give up.
*/
if (fromaddr.s_addr == 0)
return;
if (rq->bp_file[0] == 0) {
/* client didnt specify file */
if (hp == (struct hosts *)0 || hp->bootfile[0] == 0)
strcpy(file, defaultboot);
else
strcpy(file, hp->bootfile);
} else {
/* client did specify file */
strcpy(file, (const char *)rq->bp_file);
}
if (file[0] == '/') /* if absolute pathname */
strcpy(path, file);
else { /* else look in boot directory */
strcpy(path, homedir);
strcat(path, "/");
strcat(path, file);
}
/* try first to find the file with a ".host" suffix */
n = strlen(path);
if (hp != (struct hosts *)0 && hp->host[0] != 0) {
strcat(path, ".");
strcat(path, hp->host);
}
if (access(path, R_OK) < 0) {
path[n] = 0; /* try it without the suffix */
if (access(path, R_OK) < 0) {
/*
* We don't have the file. Don't respond unless
* the client asked for us by name, in case some
* other server does have the file. If he asked
* for us by name, send him back a null pathname
* so that he knows we don't have his boot file.
*/
if (rq->bp_sname[0] == 0)
return; /* didnt ask for us */
syslog(LOG_ERR, "%s requested boot file %s: access failed: %m",
iaddr_ntoa(fromaddr), path);
path[0] = '\0';
}
}
if (path[0] != '\0') {
if (strlen(path) > sizeof(rp.bp_file)-1) {
syslog(LOG_ERR, "%s: reply boot file name %s too long",
iaddr_ntoa(fromaddr), path);
path[0] = '\0';
} else
syslog(LOG_INFO, "reply to %s: boot file %s",
iaddr_ntoa(fromaddr), path);
}
strcpy((char *)rp.bp_file, path);
sendreply(&rp, 0, has_sname, 0, BOOTSTRAP_REPLY);
}
/*
* Select the address of the interface on this server that is
* on the same net as the 'from' address.
*
* Return value
* TRUE if match
* FALSE if no match
*/
int
bestaddr(iaddr_t *from, iaddr_t *answ)
{
register struct netaddr *np;
int match = 0;
if (from->s_addr == 0) {
answ->s_addr = myhostaddr.s_addr;
} else {
/*
* Search the table of nets to which the server is connected
* for the net containing the source address.
*/
for (np = nets; np->netmask != 0; np++)
if ((from->s_addr & np->netmask) == np->net) {
answ->s_addr = np->myaddr.s_addr;
match = 1;
break;
}
/*
* If no match in table, default to our 'primary' address
*/
if (np->netmask == 0)
answ->s_addr = myhostaddr.s_addr;
}
return match;
}
/*
* Forward a BOOTREQUEST packet to another server.
*
* RFC 951 (7.3) implies no-forward in case
* bp_ciaddr = 0.
*/
void
forward(register struct bootp *bp, register iaddr_t *from, register iaddr_t *dest)
{
struct sockaddr_in to;
/*
* If hop >= 3, just discard(RFC951 says so).
*/
if (bp->bp_hops >= 3)
return;
bp->bp_hops++;
/*
* If giaddr is 0, then I am the immediate gateway.
* So, set myaddr for successing forwarders to send
* reply to me directly.
*/
if (!bp->bp_giaddr.s_addr) {
(void) bestaddr(from, &bp->bp_giaddr);
}
to.sin_family = AF_INET;
to.sin_port = htons(IPPORT_BOOTPS);
to.sin_addr.s_addr = dest->s_addr; /* already in network order */
if (debug_f)
syslog(LOG_DEBUG, "forwarding BOOTP request to %s(%s)",
bp->bp_sname, iaddr_ntoa(*dest));
if (sendto(s, (caddr_t)bp, sizeof *bp, 0, &to, sizeof to) < 0)
syslog(LOG_ERR, "forwarding to %s failed (%m)",
iaddr_ntoa(to.sin_addr));
}
/*
* Read bootptab database file. Avoid rereading the file if the
* write date hasnt changed since the last time we read it.
*/
void
readtab(void)
{
struct stat st;
register char *cp;
int v;
register u_long i;
char temp[64], tempcpy[64];
register struct hosts *hp;
int skiptopercent;
if (fp == 0) {
if ((fp = log_fopen(bootptab, "r")) == NULL) {
syslog(LOG_ERR, "can't open %s", bootptab);
exit(1);
}
}
fstat(fileno(fp), &st);
if (st.st_mtime == modtime && st.st_nlink)
return; /* hasnt been modified or deleted yet */
fclose(fp);
if ((fp = log_fopen(bootptab, "r")) == NULL) {
syslog(LOG_ERR, "can't open %s", bootptab);
exit(1);
}
fstat(fileno(fp), &st);
if (debug_f)
syslog(LOG_DEBUG, "(re)reading %s", bootptab);
modtime = st.st_mtime;
homedir[0] = defaultboot[0] = 0;
nhosts = 0;
hp = &hosts[0];
linenum = 0;
skiptopercent = 1;
/*
* read and parse each line in the file.
*/
for (;;) {
if (fgets(line, sizeof line, fp) == NULL)
break; /* done */
if ((i = strlen(line)))
line[i-1] = 0; /* remove trailing newline */
linep = line;
linenum++;
if (line[0] == '#' || line[0] == 0 || line[0] == ' ')
continue; /* skip comment lines */
/* fill in fixed leading fields */
if (homedir[0] == 0) {
getfield(homedir, sizeof homedir);
continue;
}
if (defaultboot[0] == 0) {
getfield(defaultboot, sizeof defaultboot);
continue;
}
if (skiptopercent) { /* allow for future leading fields */
if (line[0] != '%')
continue;
skiptopercent = 0;
continue;
}
/* fill in host table */
getfield(hp->host, sizeof hp->host);
getfield(temp, sizeof temp);
sscanf(temp, "%d", &v);
hp->htype = v;
getfield(temp, sizeof temp);
strcpy(tempcpy, temp);
cp = tempcpy;
/* parse hardware address */
for (i = 0 ; i < sizeof hp->haddr ; i++) {
char *cpold;
char c;
cpold = cp;
while (*cp != '.' && *cp != ':' && *cp != 0)
cp++;
c = *cp; /* save original terminator */
*cp = 0;
cp++;
if (sscanf(cpold, "%x", &v) != 1)
goto badhex;
hp->haddr[i] = v;
if (c == 0)
break;
}
if (hp->htype == 1 && i != 5) {
badhex: syslog(LOG_ERR, "bad hex address: %s at line %d of bootptab",
temp, linenum);
continue;
}
getfield(temp, sizeof temp);
i = inet_addr(temp);
if (i == -1) {
register struct hostent *hep;
hep = gethostbyname(temp);
if (hep != 0 && hep->h_addrtype == AF_INET)
i = *(int *)(hep->h_addr_list[0]);
}
if (i == -1 || i == 0) {
syslog(LOG_ERR, "bad internet address: %s at line %d of bootptab",
temp, linenum);
continue;
}
hp->iaddr.s_addr = i;
getfield(hp->bootfile, sizeof hp->bootfile);
if (++nhosts >= MHOSTS) {
syslog(LOG_ERR, "bootptab exceeds max. number of hosts (%d)", MHOSTS);
exit(1);
}
hp++;
}
}
/*
* Get next field from 'line' buffer into 'str'. 'linep' is the
* pointer to current position.
*/
void
getfield(char *str, int len)
{
register char *cp = str;
for ( ; *linep && (*linep == ' ' || *linep == '\t') ; linep++)
; /* skip spaces/tabs */
if (*linep == 0) {
*cp = 0;
return;
}
len--; /* save a spot for a null */
for ( ; *linep && *linep != ' ' && *linep != '\t' ; linep++) {
*cp++ = *linep;
if (--len <= 0) {
*cp = 0;
syslog(LOG_ERR, "string truncated: %s, on line %d of bootptab",
str, linenum);
return;
}
}
*cp = 0;
}
/*
* Setup the arp cache so that IP address 'ia' will be temporarily
* bound to hardware address 'ha' of length 'len'.
*/
void
setarp(iaddr_t *ia, u_char *ha, int len)
{
struct sockaddr_in *si;
struct arpreq arpreq; /* arp request ioctl block */
arpreq.arp_pa.sa_family = AF_INET;
si = (struct sockaddr_in *)&arpreq.arp_pa;
si->sin_addr = *ia;
arpreq.arp_ha.sa_family = AF_UNSPEC;
bcopy(ha, arpreq.arp_ha.sa_data, len);
if (ioctl(s, SIOCSARP, (caddr_t)&arpreq) < 0)
syslog(LOG_ERR, "set arp ioctl failed (%m)");
}
/*
* Build a list of all the names and aliases by which
* the current host is known on all of its interfaces.
*/
void
makenamelist(void)
{
register struct hostent *hp;
char name[64];
numaddrs = 0;
myaddrs = (u_int *)malloc(max_addr_alloc*sizeof(u_int));
/*
* Get name of host as told to the kernel and look that
* up in the hostname database.
*/
gethostname(name, sizeof(name));
if ((hp = gethostbyname(name)) == 0) {
syslog(LOG_ERR, "gethostbyname(%s) failed: %s", name, hstrerror(h_errno));
exit(1);
}
strcpy(myname, name);
/*
* Remember primary Internet address
*/
myhostaddr.s_addr = *(u_long *)(hp->h_addr_list[0]);
}