/*
  Last updated : Sat Feb 12 02:55:21 1994
    J.S.Peatfield@amtp.cam.ac.uk
    
  Tweaked for NET3  Alan Cox 15/04/94
*/

/* Standard headers */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <linux/if.h>
#include <sys/time.h>
#include <unistd.h>
#include <string.h>
#include <getopt.h>
#include <stdlib.h>

/* local headers */
#include "bootp.h"

/* Needed for getopt stuff */
extern char *optarg;
extern int optind, opterr, optopt;

/* My forward declarations */
void FatalError();

void ParseCookie(unsigned char *cookie,
		 unsigned char *match,
		 struct in_addr my_addr) ;

void PrintList(char *name,
	       unsigned char *cookie,
	       int pos,
	       int len) ;

void PrintString(char *name,
	       unsigned char *cookie,
	       int pos,
	       int len) ;

/* My global variables */
static int verbose = 0; /* verbose mode or not 10/02/94 JSP */
static int debug = 0 ;  /* debug mode or not 14/02/94 JSP */
static int subnet = 0 ; /* if we have seen a subnet TAG yet (sigh) */

int main(int argc,
	 char *argv[])
{
  int sockfd;
  struct sockaddr_in cli_addr, serv_addr;
  struct bootp bppdu;
  struct ifreq ifr;
  fd_set rfds, wfds, xfds;
  struct timeval timeout ;
  long rancopy ;
  struct in_addr temp_addr ;
  int retry_wait, waited;
  int one=1, i ;
  struct timeval tp;
  char *device, *bootfile ;
/* See RFC1497, RFC1542  09/02/94   JSP  */
  unsigned char mincookie[] = {99,130,83,99,255} ;
  unsigned char *my_addr;

/* defaults unless overridden by command line options 10/02/94  JSP */
  device = "eth0" ;
  bootfile = "linux" ;

  while (1) {
    int option_index = 0, option ;
    static struct option long_options[] = {
      {"bootfile", 1, 0, 1},
      {"dev", 1, 0, 2},
      {"verbose", 0, 0, 3},
      {"debug", 0, 0, 4},
      {"help", 0, 0, 100},
      {0, 0, 0, 0},
    } ;

    option = getopt_long (argc, argv, "", long_options, &option_index);

    if (option == -1)
      break ;

    switch (option) {
    case 1:  /* New bootfile */
      bootfile = optarg ;
      if (strlen(bootfile) > 127) { /* buffer space for 128 only */
	if (verbose)
	  fprintf(stderr, "Bootfile %s too long, truncating\n", bootfile) ;
	bootfile[127] = 0;
      }
      break ;
    case 2:  /* New device */
      device = optarg ;
      if (strlen(device) > IFNAMSIZ-1) {  /* only IFNAMSIZ space in struct */
	if (verbose)
	  fprintf(stderr, "device name %s too long, truncating\n", device) ;
	device[IFNAMSIZ -1] = 0;
      }
      break ;
    case 3:
      verbose = 1 ;
      break ;
    case 4:
      debug = 1 ;
      break ;
    case 100:
      fprintf (stderr, "%s is used to find the IP number and other setup\n"
	      "information for a machine\n"
	      "\n", argv[0]) ;
    default:
      fprintf (stderr, "Usage: %s [--dev device] [--bootfile file] [--verbose] [--help]\n", argv[0]) ;
      exit (1) ;
    }
  }

  if (verbose) {
    fprintf (stderr, "BOOTPclient v0.16\n") ;
    fprintf (stderr, "  device=%s  bootfile=%s\n\n",
	     device, bootfile ) ;
  }

/* zero structure before use */
  memset((char *) &serv_addr, 0, sizeof(serv_addr));

  serv_addr.sin_family = AF_INET;
  serv_addr.sin_addr.s_addr = 0xffffffff;
  serv_addr.sin_port = htons(IPPORT_BOOTPS);

  if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
    perror("bootpc: socket failed");
    FatalError();
  }
  
  if (setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&one,sizeof(one))==-1) {
    perror("bootpc: setsockopt failed");
    FatalError();
  }
  
  memset((char *) &cli_addr, 0, sizeof(cli_addr));
  cli_addr.sin_family = AF_INET;
  cli_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  cli_addr.sin_port = htons(IPPORT_BOOTPC);

  if(bind(sockfd, (struct sockaddr *) &cli_addr, sizeof(cli_addr)) < 0) {
    perror("bootpc: bind failed");
    FatalError();
  }

/* Get the hardware address */
  memcpy(ifr.ifr_name, device, strlen(device)+1);
  if(ioctl(sockfd, SIOCGIFHWADDR, &ifr) < 0) {
    perror("bootpc: ioctl failed");
    FatalError();
  }
  my_addr=ifr.ifr_hwaddr.sa_data;
/* If we have the time seed with it xor the hardware address, otherwise
   use the hardware address 12/02/94 JSP */
  if (gettimeofday(&tp, NULL) == -1)
    rancopy = 0 ;
  else
    rancopy = tp.tv_sec + tp.tv_usec ;

/* Do the XOR */
  for (i=0; i < IFHWADDRLEN ; ++i) {
    ((unsigned char *)&rancopy)[ i % sizeof(rancopy) ] ^=
      my_addr[i] ;
  }
/* and set the seed */
  srand(rancopy) ;

  if(debug) {
    fprintf(stderr, "hardware addr is :") ;
    for (i=0; i < IFHWADDRLEN ; ++i)
      fprintf (stderr, "%2.2X ", my_addr[i]) ;
    fprintf(stderr, "\n") ;
  }

/* zero structure before use */
  memset((char *) &bppdu, 0, sizeof(bppdu));
/* Now fill in the packet... */
  bppdu.bp_op = BOOTREQUEST;
  bppdu.bp_htype = 1; /* 10Mb/s Ethernet */
  bppdu.bp_hlen = 6;
  memcpy(bppdu.bp_chaddr, my_addr, IFHWADDRLEN);
  bppdu.bp_secs = 0;  /* Must start with zero here, see RFC1542 09/02/94 JSP */

/* Put in the minimal RFC1497 Magic cookie 09/02/94 JSP */
  memcpy(bppdu.bp_vend, mincookie, sizeof(mincookie));
/* Put the user precified bootfile name in place 12/02/94 */
  memcpy(bppdu.bp_file, bootfile, strlen(bootfile)+1);

  retry_wait = 2 ;
  if (verbose)
    fprintf(stderr,"BOOTPclient broadcast...\n");
  while (retry_wait <= 64){
  
/* putf a random value in here, but keep a copy to check later 09/02/94  JSP */
    bppdu.bp_xid = rancopy = rand() ;
    if (verbose) {
      fprintf(stderr,".");
      fflush(stderr);
    }

    if(sendto(sockfd, &bppdu, sizeof(bppdu), 0, 
	      (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
      perror("bootpc: sendto");
      FatalError();
    }
    
    FD_SET(sockfd,&rfds);
    FD_ZERO(&wfds);
    FD_ZERO(&xfds);

/* Randomise the delays a little as suggested in RFC1542  09/02/94  JSP */
    timeout.tv_sec = waited = retry_wait + (1+(rand() & (retry_wait-1))) ;
    timeout.tv_usec = 0;
    
    if(select(sockfd+1, &rfds, &wfds, &xfds, &timeout)<0) {
      perror("bootpc: select");
      FatalError();
    }

    if(!FD_ISSET(sockfd, &rfds)) {
      bppdu.bp_secs += waited ;  /* add time of this timeout  09/02/94  JSP */
      retry_wait = retry_wait*2;
    } else{
      if (recvfrom(sockfd, &bppdu, sizeof(bppdu), 0,
		   (struct sockaddr *)0, (int *)0) < 0){
	perror("bootpc: recvfrom");
	FatalError();
      }
      
/* Check xid is what we asked for  09/02/94  JSP */
      if (bppdu.bp_xid != rancopy) {
	fprintf(stderr, "WARNING bp_xid mismatch\n") ;
	/* Should we quit here, ignore the packet or carry on??? */
      }

      temp_addr.s_addr = bppdu.bp_siaddr.s_addr ;
      printf("SERVER=%s\n", inet_ntoa(temp_addr));

      temp_addr.s_addr = bppdu.bp_yiaddr.s_addr ;
      printf("IPADDR=%s\n", inet_ntoa(temp_addr));

/* Pass the cookie info, the mincookie to look for and our address to
   the cookie parser.  It needs our address to get the network and
   broadcast bits right if the SUBNET is defined in the cookie.
   10/02/94  JSP */

      ParseCookie((unsigned char *)&bppdu.bp_vend,
		  mincookie, temp_addr) ;

/* No SUBNET TAG in the cookie so we fake guess here */
      if (!subnet) {
	struct in_addr network ;
	int type ;
	
	if (verbose)
	  fprintf(stderr, "Guessing network from IP number\n") ;

	type = ntohl(temp_addr.s_addr) ;
	if ((type & 0x80000000) == 0) {
	  /* Class A */
	  network.s_addr = htonl(0xFF000000) ;
	} else if ((type & 0x40000000) == 0) {
	  /* Class B */
	  network.s_addr = htonl(0xFFFF0000) ;
	} else if ((type & 0x20000000) == 0) {
	  /* Class C */
	  network.s_addr = htonl(0xFFFFFF00) ;
	} else { /* GOD KNOWS... other classes are weird */
	  if (verbose)
	    fprintf(stderr, "IP number not Class A,B or C,\n"
		    "setting NETMASK to zero\n") ;
	  network.s_addr = htonl(0x00000000) ;
	}
	temp_addr.s_addr &= network.s_addr ;
	printf("NETWORK=%s\n", inet_ntoa(temp_addr));
	temp_addr.s_addr |= ~network.s_addr ;
	printf("BROADCAST=%s\n", inet_ntoa(temp_addr));
      }

      exit(0);
    }
  }
  fprintf(stderr, "\nNo response from BOOTP server\n");
  FatalError();

  return 0 ;  /* Never reached */
}
    
void FatalError()
{
  fprintf(stderr,
	  " Unable to locate an IP address for this host.\n"
	  "     ***Please report this problem**\n\n"
	  "          [Unable to continue]\n\n");

  while(1) {
    /* your eyes are getting heavy.... */
    sleep(1000) ;
  }
}

/* Parse Magic cookies as specified in RFC1497, well only the bits we
   are actually interested in...  09/02/94 JSP
*/
void ParseCookie(unsigned char *cookie,
		 unsigned char *match,
		 struct in_addr my_addr)
{
  int i=0, len ;
  struct in_addr temp ;

  if (debug) {  /* dump cookie contents in HEX 10/02/94  JSP */
    for (i=0; i<64; i++) {
      if ((i%8) ==0)
	fprintf(stderr, "\n %2.2d :", i) ;
      fprintf(stderr, " 0x%2.2X", cookie[i]) ;
    }
    fprintf(stderr, "\n") ;
  }

/* Must get the same cookie back as we sent  09/02/94  JSP */
  for (i=0; i < 4; ++i) {
    if (cookie[i] != match[i]) {
      if (verbose)
	fprintf(stderr, "RFC1497 Cookie mismatch at offset %d\n", i) ;
      return ;
    }
  }

  if (verbose)
    fprintf(stderr, "found valid RFC1497 cookie, parsing...\n") ;

/* Carry on after the cookie for other data  09/02/94  JSP */
  while (i < 64) {
    if (verbose)
      fprintf(stderr, "cookie position %d is %d\n", i, cookie[i]) ;

    switch (cookie[i]) {  /* The monster switch statement ... */
/* PAD cookie */
    case 0 :
      i++ ;
      break ;

/* SUBNET we are in */
    case 1 :
      memcpy((char *)&temp, cookie + i + 2, 4) ;
      printf("NETMASK=%s\n", inet_ntoa(temp)) ;

/* Both values are in network order so this doesn't care about the
   ordering 10/02/94 JSP */
      my_addr.s_addr &=  temp.s_addr ;
      printf("NETWORK=%s\n", inet_ntoa(my_addr)) ;
      my_addr.s_addr |= ~temp.s_addr ;
      printf("BROADCAST=%s\n", inet_ntoa(my_addr)) ;

/* defined so we know later that subnet info has been printed 11/02/94  JSP */
      subnet = 1 ;
      i += 5 ;
      break ;

/* Time of day (ignored) */
    case 2 :
      i += 5 ;
      break ;

/* IP Gateways (routers) */
    case 3 :
      len = cookie[i+1] ;
      PrintList("GATEWAYS", cookie, i+2, len) ;
      i += len + 2 ;
      break ; 

/* Timeservers (see RFC-868) */
    case 4 :
      len = cookie[i+1] ;
      PrintList("TIMESRVS", cookie, i+2, len) ;
      i += len + 2 ;
      break ; 

/* IEN-116 Nameservers */
    case 5 :
      len = cookie[i+1] ;
      PrintList("IEN116SRVS", cookie, i+2, len) ;
      i += len + 2 ;
      break ; 

/* DNS Nameservers */
    case 6 :
      len = cookie[i+1] ;
      PrintList("DNSSRVS", cookie, i+2, len) ;
      i += len + 2 ;
      break ; 

/* LOGGING servers */
    case 7 :
      len = cookie[i+1] ;
      PrintList("LOGSRVS", cookie, i+2, len) ;
      i += len + 2 ;
      break ; 

/* Quote of day/Cookie servers */
    case 8 :
      len = cookie[i+1] ;
      PrintList("QODSRVS", cookie, i+2, len) ;
      i += len + 2 ;
      break ; 

/* LPR servers */
    case 9 :
      len = cookie[i+1] ;
      PrintList("LPRSRVS", cookie, i+2, len) ;
      i += len + 2 ;
      break ; 

/* Impress (Imogen) servers */
    case 10 :
      len = cookie[i+1] ;
      PrintList("IMPRESSSRVS", cookie, i+2, len) ;
      i += len + 2 ;
      break ; 

/* Remote Location Protocol servers */
    case 11 :
      len = cookie[i+1] ;
      PrintList("RLPSRVS", cookie, i+2, len) ;
      i += len + 2 ;
      break ; 

/* HOSTNAME (may be fqdn or leaf) */
    case 12 :
      len = cookie[i+1] ;
      PrintString("HOSTNAME", cookie, i+2, len) ;
      i += len + 2 ;
      break ;

/* BOOT File Size (ignored) */
    case 13 :
      i += 3 ;
      break ;

/* Merit DUMP File name (ignored) */
    case 14 :
      len = cookie[i+1] ;
      i += len + 2 ;
      break ;

/* DOMAIN */
    case 15 :
      len = cookie[i+1] ;
      PrintString("DOMAIN", cookie, i+2, len) ;
      i += len + 2 ;
      break ;

/* SWAPServer address */
    case 16 :
      memcpy((char *)&temp, cookie + i + 2, 4) ;
      printf("SWAPSRVR=%s\n", inet_ntoa(temp)) ;
      i += 5 ;
      break ;

/* Root pathname to mount as root filesystem (ignored) */
    case 17 :
      len = cookie[i+1] ;
      i += len + 2 ;
      break ;

/* Extensions.  Name of further Cookie data (ignored) */
    case 18 :
      len = cookie[i+1] ;
      i += len + 2 ;
      break ;
      
/* END of cookie (phew) */
    case 255 :
      if (verbose)
	fprintf(stderr, "end of cookie parsing, END tag found\n") ;
      return ;

    default:
      if (verbose)
	fprintf(stderr, "Not understood TAG %d at %d\n", cookie[i], i) ;
      ++i ;
      break ;
    }
  }
}

/* Print out a list of IP numbers */
void PrintList(char *name,
	       unsigned char *cookie,
	       int pos,
	       int len)
{
  struct in_addr temp ;

  if (verbose)
    fprintf(stderr, "%s found len=%d\n", name, len) ;

  if ((len % 4) != 0) {
    if (verbose)
      fprintf (stderr, "ERROR %s length (%d) not 4 div\n", name, len) ;
    return ;
  }
  if (len == 0) /* Nothing to do  10/02/94  JSP */
    return ;

  printf("%s=\"", name) ;
  for ( ; len; len -= 4, pos += 4) {
    memcpy((char *)&temp, cookie + pos, 4) ;
    printf("%s", inet_ntoa(temp)) ;
    if (len > 4)
      putchar(' ') ;
  }
  printf("\"\n") ;
}

/* Prints the string passed */
void PrintString(char *name,
	       unsigned char *cookie,
	       int pos,
	       int len)
{
  printf("%s=\"", name) ;
  for (; len ; ++pos, --len) {
    putchar(cookie[pos]) ;
  }
  printf ("\"\n") ;
}
