/* A Boot Protocol (bootp) server. */

/* Modified March 1988 by Jon Rochlis, MIT Telecom, to add syslog
     and support ethernet */

/* Compile -DINETD to get something to run from inetd */

/*
 *------------------------------------------------------------------
 *
 * $Source: /mit/cgw/src/tftp.vax/RCS/bootpd.c,v $
 * $Revision: 1.4 $
 * $Date: 88/07/11 18:19:00 $
 * $State: Exp $
 * $Author: jon $
 * $Locker: jon $
 *
 * $Log:	bootpd.c,v $
 * Revision 1.4  88/07/11  18:19:00  jon
 * Add's Sommerfeld's line to permit responses even when the hardware
 * address isn't in the config file (but a "*" entry is and gname matches).
 * 
 * Revision 1.3  88/06/10  21:20:05  root
 * Add the old hack of setting the outgoing address to the client ip addr
 * if HTYPE is a proteon ring, since that will make it go out the right 
 * interface and arp isn't an issue.
 * 
 * Also set REUSERADDR under inetd so bind won't sometimes fail (I hope).
 * 
 * 
 *------------------------------------------------------------------
 */

#ifndef lint
static char *rcsid_bootpd_c = "$Header: bootpd.c,v 1.4 88/07/11 18:19:00 jon Locked $";
#endif	lint

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netdb.h>
#include <syslog.h>
#include "bootp.h"

#define BUF_LEN 128

char conf_file[] = "/etc/bootpd.conf";
struct sockaddr_in fhost;	/* Host the bootp packet was recieved from */
struct bootp_pkt pkt;

#ifndef INETD
int net_socket;
#endif
  
main()
{
  int err;

  openlog ("bootpd", 0, LOG_LOCAL6);

#ifndef INETD
  err = bind_socket();
  if (err != 0) exit (err);

  for (;;) do_main();
#else
  err = do_main();
  exit (err);
#endif;
}

int do_main()
{
  int err;

  err = receive_bootp_pkt();
  if (err != 0) return (err);
    
  syslog (LOG_INFO, "Bootp packet from host %s", inet_ntoa (fhost.sin_addr));

  err = read_conf_file();
  if (err != 0) return (err);

  syslog (LOG_NOTICE, "Boot file %s for host %d.%d.%d.%d\n", pkt.bp_file,
	  pkt.bp_yiaddr&0xff, (pkt.bp_yiaddr>>8)&0xff,
	  (pkt.bp_yiaddr>>16)&0xff, (pkt.bp_yiaddr>>24)&0xff);

  err = send_bootp_reply();
  return (err);
}


/* Reads the bootp packet from the net. */
int receive_bootp_pkt()
{
    int len;
    int s;

#ifdef INETD
    s = 0;
#else
    s = net_socket;
#endif
    len = recvfrom(s, &pkt, sizeof(pkt), 0, &fhost, sizeof(fhost));
    if (len == -1) {
	syslog (LOG_ERR, "%m. while receiving packet");
	return (-2);
    }

    if (pkt.bp_op != BOOTREQUEST) {
	syslog (LOG_INFO, "Opcode %d not request\n", pkt.bp_op);
	return(1);
    }
    return(0);
}


/* Reads the configuration file and looks at the information from the
 * client to figure out what file to boot. */
int read_conf_file()
{
    FILE *cfile;
    char line_buf[BUF_LEN];
    char boot_dir[BUF_LEN];
    char gname[BUF_LEN], hname[BUF_LEN], haddr[BUF_LEN];
    char fname[BUF_LEN], iaddr_string[BUF_LEN];
    long iaddr;
    int htype;
    int res;
    int line;
    struct in_addr host;
    int e[6], i, found_it;

    if ((cfile = fopen(conf_file, "r")) == NULL) {
      syslog (LOG_ERR, "Couldn't open configuration file: %s", conf_file);
      return (1);
    }

    switch (pkt.bp_htype) {
    case VII_HTYPE:
      syslog (LOG_DEBUG, "htype: %-4d hlen: %-4d haddr: %-4x",
	      pkt.bp_htype, pkt.bp_hlen, *(long *)pkt.bp_chaddr);
      break;
    case ETHER_HTYPE: 
      /* for some strange reason these don't work as one syslog call */
      syslog (LOG_DEBUG, "htype: %-4d hlen: %-4d", pkt.bp_htype, pkt.bp_hlen);
      syslog (LOG_DEBUG, "haddr: %02x:%02x:%02x:%02x:%02x:%02x", 
	      pkt.bp_chaddr[0], pkt.bp_chaddr[1], pkt.bp_chaddr[2],
	      pkt.bp_chaddr[3], pkt.bp_chaddr[4], pkt.bp_chaddr[5]);
      break;
    default:
      syslog (LOG_DEBUG, "htype: %-4d hlen: %-4d", pkt.bp_htype, pkt.bp_hlen);
    }
	       
    host.s_addr = pkt.bp_ciaddr;

    syslog (LOG_DEBUG, "Client addr %s", inet_ntoa (host));
    host.s_addr = pkt.bp_giaddr;

    syslog (LOG_DEBUG, "Gateway addr %s", inet_ntoa (host));
    if (strlen(pkt.bp_file) != 0)
      syslog (LOG_DEBUG, "Generic name is %s", pkt.bp_file);
    else
      syslog (LOG_DEBUG, "Boot default file");

    /* needs to be fixed - bp_chaddr really isn't a character string */
/*    if (strlen(pkt.bp_chaddr) == 0 && pkt.bp_ciaddr == 0) {
	fprintf(stderr, "Need either harware addr or IP addr\n");
	return (-1);
    } */

    line = 0;
    /* Read boot directory */
    for (;;) {
	line++;
	if (fgets(line_buf, BUF_LEN, cfile) == NULL) {
	  syslog (LOG_ERR, "Unexpected EOF\n");
	  return (2);
	}
	if (line_buf[0] == '#')
	  continue;
	if (( res = sscanf(line_buf, " %s", boot_dir)) == EOF)
	  continue;
	if (res < 1) {
	  syslog (LOG_ERR, "Error on line %d\n", line);
	    continue;
	}
	break;
    }

    syslog (LOG_DEBUG, "Boot directory = %s\n", boot_dir);

    /* Read address mappings and figure out which file to boot. */

    for (;;) {
	line++;
	if (fgets(line_buf, BUF_LEN, cfile) == NULL)
	  break;
	if (line_buf[0] == '#')
	  continue;
	res = sscanf(line_buf, " %s %d %s %s %s %s",
		     hname, &htype, haddr, iaddr_string,
		     fname, gname);
	if (res == EOF)
	  continue;
	if (res < 5) {
	  syslog (LOG_ERR, "Error on line %d\n", line);
	    continue;
	}
	iaddr = inet_addr(iaddr_string);
	if (res < 6) gname[0] = '\0';

#ifdef ndef
	if (res < 6)
	  syslog (LOG_DEBUG, "%15s %2d %18s %15s %8s %-8s\n",
		 hname, htype, haddr, iaddr_string,
		 fname, "*DEFAULT*");
	else
	  syslog (LOG_DEBUG, "%15s %2d %18s %15s %8s %-8s\n",
		 hname, htype, haddr, iaddr_string,
		 fname, gname);
#endif

	/* See if this is the right one */
	if (pkt.bp_htype != htype)
	  continue;
	if (pkt.bp_ciaddr) {
	    if (pkt.bp_ciaddr != iaddr)
	      continue;
	}
	else {
	    /* htype specific hardware address matching */
	    switch(htype) {
	    case VII_HTYPE:		/* vii ringnet */
		if (pkt.bp_chaddr[0] != atoi(haddr))
		  continue;
		break;
	    case ETHER_HTYPE:		/* ethernet */
		found_it = 1;
		if (!strcmp(haddr, "*")) break; /* so we don't have to put
						   each athena machine's 
						   ether addr in the file */
		sscanf (haddr, "%2x:%2x:%2x:%2x:%2x:%2x",
			&e[0], &e[1], &e[2], &e[3], &e[4], &e[5]);
		for (i = 0; i != 6; i++)
		  if (e[i] != pkt.bp_chaddr[i]) found_it = 0;
		if (found_it) break;
		continue;
	    default:
		continue;
	    }
	}
	if (strcmp(pkt.bp_file, gname))
	  continue;

	if (fname[0] == '/') {
	    strcpy(pkt.bp_file, fname);
	}
	else {
	    char *ptr;

	    strcpy(pkt.bp_file, boot_dir);
	    ptr = &pkt.bp_file[strlen(boot_dir)];
	    *ptr++ = '/';
	    strcpy(ptr, fname);
	}
	pkt.bp_yiaddr = iaddr;

	return 0;
    }
    return 1;
}

/* Send the bootp reply, either to the host requesting the boot, or to
 * the gateway which forwarded the bootp packet. */
int send_bootp_reply()
{
    int f, len, on = 1, n;
    long addr;
    struct sockaddr_in sin, my_sin;
    struct servent *sp;    

    pkt.bp_op = BOOTREPLY;

    if (pkt.bp_ciaddr != 0) {
	addr = pkt.bp_ciaddr;
	sp = getservbyname("bootcli", "udp");
	if (sp == 0) {
	  syslog (LOG_ERR, "udp/bootcli: unknown service\n");
	  return (2);
	}
    }
    else if (pkt.bp_giaddr != 0) {
	addr = pkt.bp_giaddr;
	sp = getservbyname("bootser", "udp");
	if (sp == 0) {
	  syslog (LOG_ERR, "udp/bootser: unknown service\n");
	  return (1);
	}
    }
    else {
	syslog (LOG_DEBUG, "Need to broadcast response\n");
	/* Really need to broadcast response here but since are on a
	 * ring this will work.  If could figure out a way to
	 * determine whether need to broadcast or not would be nice
	 * but I don't think is possible. */

	if (pkt.bp_htype == VII_HTYPE)
	  addr = pkt.bp_yiaddr;  /* hack, hack. But this will work and make it
				    go out the right interface! */
	else addr = INADDR_BROADCAST;

	sp = getservbyname("bootcli", "udp");
	if (sp == 0) {
	  syslog (LOG_ERR, "udp/bootcli: unknown service\n");
	  return (1);
	}
    }

    bzero((char *)&my_sin, sizeof (my_sin));
    my_sin.sin_family = AF_INET;

    /* find out what interface address we're going to use */
    my_address_socket = socket(AF_INET, SOCK_DGRAM, 0);
    if (my_address_socket < 0) {
      syslog (LOG_ERR, "%m. socket");
      return (3);
    }

    if (bind(f, &sin, sizeof (sin)) < 0) {
      syslog (LOG_ERR, "%m. bind");
      return(1);
    }

    n = sizeof sin;
    if (getsockname (my_address_socket, &my_sin, &n) != 0) {
      syslog (LOG_ERR, "%m. getsockname");
      return (3);
    }
    if (n != sizeof (sin)) {
      syslog (LOG_ERR, "%m. getsockname");
      return (3);
    }

    bzero((char *)&sin, sizeof (sin));
    sin.sin_family = AF_INET;
    sin.sin_port = sp->s_port;

    pkt.bp_siaddr = my_sin.sin_addr.s_addr;

    if (pkt.bp_siaddr
    sin.sin_addr.s_addr = addr;

    syslog (LOG_DEBUG, "sending to %s", inet_ntoa (sin.sin_addr));

    if (addr == INADDR_BROADCAST) {
      if (setsockopt(f, SOL_SOCKET, SO_BROADCAST, (char *)&on, 
		     sizeof(on)) < 0) {
	syslog(LOG_ERR, "setsockopt (SO_BROADCAST): %m");
	return (-1);
      }
    }

    len = sendto(f, &pkt, sizeof(pkt), 0, &sin, sizeof(sin));
    if (len < 0) {
      syslog (LOG_ERR, "%m. sendto");
      return (-1);
    }
    return (0);
}

#ifndef INETD
bind_socket ()
{
  struct sockaddr_in sin;
  struct servent *sp;    

  sp = getservbyname("bootser", "udp");
  if (sp == 0) {
    syslog (LOG_ERR, "udp/bootser: unknown service\n");
    return (1);
  }
  
  net_socket = socket(AF_INET, SOCK_DGRAM, 0);
  if (net_socket < 0) {
    syslog (LOG_ERR, "%m. creating socket.");
    return (1);
  }

  bzero((char *)&sin, sizeof (sin));
  sin.sin_family = AF_INET;
  sin.sin_port = sp->s_port;
  if (bind(net_socket, &sin, sizeof (sin)) < 0) {
    syslog (LOG_ERR, "%m. Binding socket.");
    return (1);
  }
}
#endif /* INETD */
