/*1* bootpgw.c - BOOTP GateWay2* This program forwards BOOTP Request packets to a BOOTP server.3*/45/************************************************************************6Copyright 1988, 1991 by Carnegie Mellon University78All Rights Reserved910Permission to use, copy, modify, and distribute this software and its11documentation for any purpose and without fee is hereby granted, provided12that the above copyright notice appear in all copies and that both that13copyright notice and this permission notice appear in supporting14documentation, and that the name of Carnegie Mellon University not be used15in advertising or publicity pertaining to distribution of the software16without specific, written prior permission.1718CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS19SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.20IN NO EVENT SHALL CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL21DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR22PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS23ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS24SOFTWARE.25************************************************************************/2627/*28* BOOTPGW is typically used to forward BOOTP client requests from29* one subnet to a BOOTP server on a different subnet.30*/3132#include <sys/types.h>33#include <sys/param.h>34#include <sys/socket.h>35#include <sys/ioctl.h>36#include <sys/file.h>37#include <sys/time.h>38#include <sys/stat.h>39#include <sys/utsname.h>4041#include <net/if.h>42#include <netinet/in.h>43#include <arpa/inet.h> /* inet_ntoa */4445#ifndef NO_UNISTD46#include <unistd.h>47#endif4849#include <err.h>50#include <stdlib.h>51#include <signal.h>52#include <stdio.h>53#include <string.h>54#include <errno.h>55#include <ctype.h>56#include <netdb.h>57#include <paths.h>58#include <syslog.h>59#include <assert.h>6061#ifdef NO_SETSID62# include <fcntl.h> /* for O_RDONLY, etc */63#endif6465#include "bootp.h"66#include "getif.h"67#include "hwaddr.h"68#include "report.h"69#include "patchlevel.h"7071/* Local definitions: */72#define MAX_MSG_SIZE (3*512) /* Maximum packet size */73#define TRUE 174#define FALSE 075#define get_network_errmsg get_errmsg76777879/*80* Externals, forward declarations, and global variables81*/8283static void usage(void) __dead2;84static void handle_reply(void);85static void handle_request(void);8687/*88* IP port numbers for client and server obtained from /etc/services89*/9091u_short bootps_port, bootpc_port;929394/*95* Internet socket and interface config structures96*/9798struct sockaddr_in bind_addr; /* Listening */99struct sockaddr_in recv_addr; /* Packet source */100struct sockaddr_in send_addr; /* destination */101102103/*104* option defaults105*/106int debug = 0; /* Debugging flag (level) */107struct timeval actualtimeout =108{ /* fifteen minutes */10915 * 60L, /* tv_sec */1100 /* tv_usec */111};112u_char maxhops = 4; /* Number of hops allowed for requests. */113u_int minwait = 3; /* Number of seconds client must wait before114its bootrequest packets are forwarded. */115int arpmod = TRUE; /* modify the ARP table */116117/*118* General119*/120121int s; /* Socket file descriptor */122char *pktbuf; /* Receive packet buffer */123int pktlen;124char *progname;125char *servername;126int32 server_ipa; /* Real server IP address, network order. */127128struct in_addr my_ip_addr;129130struct utsname my_uname;131char *hostname;132133134135136137/*138* Initialization such as command-line processing is done and then the139* main server loop is started.140*/141142int143main(int argc, char **argv)144{145struct timeval *timeout;146struct bootp *bp;147struct servent *servp;148struct hostent *hep;149char *stmp;150int n, ba_len, ra_len;151int nfound, readfds;152int standalone;153154progname = strrchr(argv[0], '/');155if (progname) progname++;156else progname = argv[0];157158/*159* Initialize logging.160*/161report_init(0); /* uses progname */162163/*164* Log startup165*/166report(LOG_INFO, "version %s.%d", VERSION, PATCHLEVEL);167168/* Debugging for compilers with struct padding. */169assert(sizeof(struct bootp) == BP_MINPKTSZ);170171/* Get space for receiving packets and composing replies. */172pktbuf = malloc(MAX_MSG_SIZE);173if (!pktbuf) {174report(LOG_ERR, "malloc failed");175exit(1);176}177bp = (struct bootp *) pktbuf;178179/*180* Check to see if a socket was passed to us from inetd.181*182* Use getsockname() to determine if descriptor 0 is indeed a socket183* (and thus we are probably a child of inetd) or if it is instead184* something else and we are running standalone.185*/186s = 0;187ba_len = sizeof(bind_addr);188bzero((char *) &bind_addr, ba_len);189errno = 0;190standalone = TRUE;191if (getsockname(s, (struct sockaddr *) &bind_addr, &ba_len) == 0) {192/*193* Descriptor 0 is a socket. Assume we are a child of inetd.194*/195if (bind_addr.sin_family == AF_INET) {196standalone = FALSE;197bootps_port = ntohs(bind_addr.sin_port);198} else {199/* Some other type of socket? */200report(LOG_INFO, "getsockname: not an INET socket");201}202}203/*204* Set defaults that might be changed by option switches.205*/206stmp = NULL;207timeout = &actualtimeout;208209if (uname(&my_uname) < 0)210errx(1, "can't get hostname");211hostname = my_uname.nodename;212213hep = gethostbyname(hostname);214if (!hep) {215printf("Can not get my IP address\n");216exit(1);217}218bcopy(hep->h_addr, (char *)&my_ip_addr, sizeof(my_ip_addr));219220/*221* Read switches.222*/223for (argc--, argv++; argc > 0; argc--, argv++) {224if (argv[0][0] != '-')225break;226switch (argv[0][1]) {227228case 'a': /* don't modify the ARP table */229arpmod = FALSE;230break;231case 'd': /* debug level */232if (argv[0][2]) {233stmp = &(argv[0][2]);234} else if (argv[1] && argv[1][0] == '-') {235/*236* Backwards-compatible behavior:237* no parameter, so just increment the debug flag.238*/239debug++;240break;241} else {242argc--;243argv++;244stmp = argv[0];245}246if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {247warnx("invalid debug level");248break;249}250debug = n;251break;252253case 'h': /* hop count limit */254if (argv[0][2]) {255stmp = &(argv[0][2]);256} else {257argc--;258argv++;259stmp = argv[0];260}261if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||262(n < 0) || (n > 16))263{264warnx("invalid hop count limit");265break;266}267maxhops = (u_char)n;268break;269270case 'i': /* inetd mode */271standalone = FALSE;272break;273274case 's': /* standalone mode */275standalone = TRUE;276break;277278case 't': /* timeout */279if (argv[0][2]) {280stmp = &(argv[0][2]);281} else {282argc--;283argv++;284stmp = argv[0];285}286if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {287warnx("invalid timeout specification");288break;289}290actualtimeout.tv_sec = (int32) (60 * n);291/*292* If the actual timeout is zero, pass a NULL pointer293* to select so it blocks indefinitely, otherwise,294* point to the actual timeout value.295*/296timeout = (n > 0) ? &actualtimeout : NULL;297break;298299case 'w': /* wait time */300if (argv[0][2]) {301stmp = &(argv[0][2]);302} else {303argc--;304argv++;305stmp = argv[0];306}307if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||308(n < 0) || (n > 60))309{310warnx("invalid wait time");311break;312}313minwait = (u_int)n;314break;315316default:317warnx("unknown switch: -%c", argv[0][1]);318usage();319break;320321} /* switch */322} /* for args */323324/* Make sure server name argument is suplied. */325servername = argv[0];326if (!servername) {327warnx("missing server name");328usage();329}330/*331* Get address of real bootp server.332*/333if (isdigit(servername[0]))334server_ipa = inet_addr(servername);335else {336hep = gethostbyname(servername);337if (!hep)338errx(1, "can't get addr for %s", servername);339bcopy(hep->h_addr, (char *)&server_ipa, sizeof(server_ipa));340}341342if (standalone) {343/*344* Go into background and disassociate from controlling terminal.345* XXX - This is not the POSIX way (Should use setsid). -gwr346*/347if (debug < 3) {348if (fork())349exit(0);350#ifdef NO_SETSID351setpgrp(0,0);352#ifdef TIOCNOTTY353n = open(_PATH_TTY, O_RDWR);354if (n >= 0) {355ioctl(n, TIOCNOTTY, (char *) 0);356(void) close(n);357}358#endif /* TIOCNOTTY */359#else /* SETSID */360if (setsid() < 0)361perror("setsid");362#endif /* SETSID */363} /* if debug < 3 */364/*365* Nuke any timeout value366*/367timeout = NULL;368369/*370* Here, bootpd would do:371* chdir372* tzone_init373* rdtab_init374* readtab375*/376377/*378* Create a socket.379*/380if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {381report(LOG_ERR, "socket: %s", get_network_errmsg());382exit(1);383}384/*385* Get server's listening port number386*/387servp = getservbyname("bootps", "udp");388if (servp) {389bootps_port = ntohs((u_short) servp->s_port);390} else {391bootps_port = (u_short) IPPORT_BOOTPS;392report(LOG_ERR,393"bootps/udp: unknown service -- using port %d",394bootps_port);395}396397/*398* Bind socket to BOOTPS port.399*/400bind_addr.sin_family = AF_INET;401bind_addr.sin_port = htons(bootps_port);402bind_addr.sin_addr.s_addr = INADDR_ANY;403if (bind(s, (struct sockaddr *) &bind_addr,404sizeof(bind_addr)) < 0)405{406report(LOG_ERR, "bind: %s", get_network_errmsg());407exit(1);408}409} /* if standalone */410/*411* Get destination port number so we can reply to client412*/413servp = getservbyname("bootpc", "udp");414if (servp) {415bootpc_port = ntohs(servp->s_port);416} else {417report(LOG_ERR,418"bootpc/udp: unknown service -- using port %d",419IPPORT_BOOTPC);420bootpc_port = (u_short) IPPORT_BOOTPC;421}422423/* no signal catchers */424425/*426* Process incoming requests.427*/428for (;;) {429struct timeval tv;430431readfds = 1 << s;432if (timeout)433tv = *timeout;434435nfound = select(s + 1, (fd_set *)&readfds, NULL, NULL,436(timeout) ? &tv : NULL);437if (nfound < 0) {438if (errno != EINTR) {439report(LOG_ERR, "select: %s", get_errmsg());440}441continue;442}443if (!(readfds & (1 << s))) {444report(LOG_INFO, "exiting after %ld minutes of inactivity",445(long)(actualtimeout.tv_sec / 60));446exit(0);447}448ra_len = sizeof(recv_addr);449n = recvfrom(s, pktbuf, MAX_MSG_SIZE, 0,450(struct sockaddr *) &recv_addr, &ra_len);451if (n <= 0) {452continue;453}454if (debug > 3) {455report(LOG_INFO, "recvd pkt from IP addr %s",456inet_ntoa(recv_addr.sin_addr));457}458if (n < sizeof(struct bootp)) {459if (debug) {460report(LOG_INFO, "received short packet");461}462continue;463}464pktlen = n;465466switch (bp->bp_op) {467case BOOTREQUEST:468handle_request();469break;470case BOOTREPLY:471handle_reply();472break;473}474}475return 0;476}477478479480481/*482* Print "usage" message and exit483*/484485static void486usage()487{488fprintf(stderr,489"usage: bootpgw [-a] [-i | -s] [-d level] [-h count] [-t timeout]\n"490" [-w time] server\n");491fprintf(stderr, "\t -a\tdon't modify ARP table\n");492fprintf(stderr, "\t -d n\tset debug level\n");493fprintf(stderr, "\t -h n\tset max hop count\n");494fprintf(stderr, "\t -i\tforce inetd mode (run as child of inetd)\n");495fprintf(stderr, "\t -s\tforce standalone mode (run without inetd)\n");496fprintf(stderr, "\t -t n\tset inetd exit timeout to n minutes\n");497fprintf(stderr, "\t -w n\tset min wait time (secs)\n");498exit(1);499}500501502503/*504* Process BOOTREQUEST packet.505*506* Note, this just forwards the request to a real server.507*/508static void509handle_request()510{511struct bootp *bp = (struct bootp *) pktbuf;512u_short secs;513u_char hops;514515/* XXX - SLIP init: Set bp_ciaddr = recv_addr here? */516517if (debug) {518report(LOG_INFO, "request from %s",519inet_ntoa(recv_addr.sin_addr));520}521/* Has the client been waiting long enough? */522secs = ntohs(bp->bp_secs);523if (secs < minwait)524return;525526/* Has this packet hopped too many times? */527hops = bp->bp_hops;528if (++hops > maxhops) {529report(LOG_NOTICE, "request from %s reached hop limit",530inet_ntoa(recv_addr.sin_addr));531return;532}533bp->bp_hops = hops;534535/*536* Here one might discard a request from the same subnet as the537* real server, but we can assume that the real server will send538* a reply to the client before it waits for minwait seconds.539*/540541/* If gateway address is not set, put in local interface addr. */542if (bp->bp_giaddr.s_addr == 0) {543#if 0 /* BUG */544struct sockaddr_in *sip;545struct ifreq *ifr;546/*547* XXX - This picks the wrong interface when the receive addr548* is the broadcast address. There is no portable way to549* find out which interface a broadcast was received on. -gwr550* (Thanks to <[email protected]> for finding this bug!)551*/552ifr = getif(s, &recv_addr.sin_addr);553if (!ifr) {554report(LOG_NOTICE, "no interface for request from %s",555inet_ntoa(recv_addr.sin_addr));556return;557}558sip = (struct sockaddr_in *) &(ifr->ifr_addr);559bp->bp_giaddr = sip->sin_addr;560#else /* BUG */561/*562* XXX - Just set "giaddr" to our "official" IP address.563* RFC 1532 says giaddr MUST be set to the address of the564* interface on which the request was received. Setting565* it to our "default" IP address is not strictly correct,566* but is good enough to allow the real BOOTP server to567* get the reply back here. Then, before we forward the568* reply to the client, the giaddr field is corrected.569* (In case the client uses giaddr, which it should not.)570* See handle_reply()571*/572bp->bp_giaddr = my_ip_addr;573#endif /* BUG */574575/*576* XXX - DHCP says to insert a subnet mask option into the577* options area of the request (if vendor magic == std).578*/579}580/* Set up socket address for send. */581send_addr.sin_family = AF_INET;582send_addr.sin_port = htons(bootps_port);583send_addr.sin_addr.s_addr = server_ipa;584585/* Send reply with same size packet as request used. */586if (sendto(s, pktbuf, pktlen, 0,587(struct sockaddr *) &send_addr,588sizeof(send_addr)) < 0)589{590report(LOG_ERR, "sendto: %s", get_network_errmsg());591}592}593594595596/*597* Process BOOTREPLY packet.598*/599static void600handle_reply()601{602struct bootp *bp = (struct bootp *) pktbuf;603struct ifreq *ifr;604struct sockaddr_in *sip;605unsigned char *ha;606int len, haf;607608if (debug) {609report(LOG_INFO, " reply for %s",610inet_ntoa(bp->bp_yiaddr));611}612/* Make sure client is directly accessible. */613ifr = getif(s, &(bp->bp_yiaddr));614if (!ifr) {615report(LOG_NOTICE, "no interface for reply to %s",616inet_ntoa(bp->bp_yiaddr));617return;618}619#if 1 /* Experimental (see BUG above) */620/* #ifdef CATER_TO_OLD_CLIENTS ? */621/*622* The giaddr field has been set to our "default" IP address623* which might not be on the same interface as the client.624* In case the client looks at giaddr, (which it should not)625* giaddr is now set to the address of the correct interface.626*/627sip = (struct sockaddr_in *) &(ifr->ifr_addr);628bp->bp_giaddr = sip->sin_addr;629#endif630631/* Set up socket address for send to client. */632send_addr.sin_family = AF_INET;633send_addr.sin_addr = bp->bp_yiaddr;634send_addr.sin_port = htons(bootpc_port);635636if (arpmod) {637/* Create an ARP cache entry for the client. */638ha = bp->bp_chaddr;639len = bp->bp_hlen;640struct in_addr dst;641642if (len > MAXHADDRLEN)643len = MAXHADDRLEN;644haf = (int) bp->bp_htype;645if (haf == 0)646haf = HTYPE_ETHERNET;647648if (debug > 1)649report(LOG_INFO, "setarp %s - %s",650inet_ntoa(dst), haddrtoa(ha, len));651setarp(s, &dst, haf, ha, len);652}653654/* Send reply with same size packet as request used. */655if (sendto(s, pktbuf, pktlen, 0,656(struct sockaddr *) &send_addr,657sizeof(send_addr)) < 0)658{659report(LOG_ERR, "sendto: %s", get_network_errmsg());660}661}662663/*664* Local Variables:665* tab-width: 4666* c-indent-level: 4667* c-argdecl-indent: 4668* c-continued-statement-offset: 4669* c-continued-brace-offset: -4670* c-label-offset: -4671* c-brace-offset: 0672* End:673*/674675676