/**********************************************************************
 *               Copyright (C) 2002 Patrick McManus                    *
 *                <mcmanus@ducksong.com>                               *
 *                                                                     *
 *    This program is free software; you can redistribute it and/or    *
 *    modify it under the terms of the GNU General Public License as   *
 *    published by the Free Software Foundation; either version 2 of   *
 *    the License, or (at your option) any later version.              *
 *                                                                     *
 *    This program is distributed in the hope that it will be useful,  *
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of   *
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
 *    GNU General Public License for more details.                     *
 **********************************************************************/

/* ------------------------------------------------------------------ *
 * This code implements the DHCPINFORM method of DHCP as defined      *
 * by rfcs 2131 and 2132.                                             *
 *                                                                    *
 * It allows a userspace appliation that is not involved in           *
 * establishing dhcp leases to independently obtain configuration     *
 * information that is maintained in the dhcp system without          *
 * impacting interface configurations or existing leases.             *
 *                                                                    *
 * To read the dhcp responses the code must bind to udp port 68       *
 * that might require root priveleges (maybe suid) , or consider      *
 * accessfs.                                                          *
 *                                                                    *
 * http://www.ducksong.com:81/dhcpinform/                             *
 * ------------------------------------------------------------------ */

/*

./a.out [-v] [-a] [-h] [-s] [option] [option2] [option3] ...
        -h : This help message and exit
        -v : Context information on stderr
        -m : Gather values from multiple servers (default is first to answer)
        -a : print values on stdout for all recvd options (need -v for sanity.)
        -s : print values for matched options in structured format(*) instead of plain value
        option : DHCP option number (rfc 2132) to print value of on stdout
                 keywords wpad (252), type (53), server (54), subnet (1), 
                 router (3), dns (6), and domain (15) are also valid options

		 (*) Structured output is code:length:data[CR]

Values printed to stdout are not converted to ascii, nor are newlines added.

*/

#if 0
static char rcsid[] = "$Name:  $ $RCSfile: main.c,v $ $Revision: 1.17 $ $Date: 2002/04/20 20:10:15 $ $Author: mcmanus $";
#endif


#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <sys/poll.h>


/* DHCP Message Offsets */

#define F_OP         0
#define F_HTYPE      1
#define F_HLEN       2
#define F_HOPS       3
#define F_XID        4
#define F_SECS       8
#define F_FLAGS      10
#define F_CIADDR     12
#define F_YIADDR     16
#define F_SIADDR     20
#define F_GIADDR     24
#define F_CHADDR     28
#define F_SNAME      44
#define F_FILE       108
#define F_OPTIONS    236

/* my standard options */
#define F_COOKIE F_OPTIONS
#define F_INFORM 240
#define F_WPAD   243
#define F_ML_H   245
#define F_ML_D   247

#define F_CUSTOM 249

int gargc,structured_output=0,multiple_servers=0;
char **gargv;

void bail (char *reason)
{
  fprintf (stderr,"%s\n",reason);
  exit (1);
}

int bind_r (unsigned int localip)
{
  int s;
  struct sockaddr_in srs;
  
  if ((s = socket (AF_INET,SOCK_DGRAM, 0))==-1)
    return -1;
  
  memset (&srs,0,sizeof (struct sockaddr_in));

  srs.sin_family = AF_INET;
  srs.sin_port = htons ((short)68);
  srs.sin_addr.s_addr = localip;

  if (bind (s,(struct sockaddr *) &srs,sizeof (struct sockaddr_in)) == -1)
    {
      close (s);
      return -1;
    }
  return s;
}


int display_options (unsigned char *msg, int tl, char *cookie)
{
  int i,code,len,j;
  
  if (memcmp (cookie,msg+F_OPTIONS,4))
    bail ("cookie mismatch");
  
  for (i=F_OPTIONS+4;i<tl;)
    {
      code= msg[i];
      if ( (code == 255) || (code == 0))
	{
	  len = 0;
	  i++;
	}
      else
	{
	  len=msg[i+1];
	  i += 2+len;
	}
      
      fprintf (stderr,"code %d, len %d\n",code,len);
      fflush (stderr);
      
      if (code == 255)		/* end of options */
	break;

      for (j=1;j<gargc;j++)
	if ((!strcmp ("-a",gargv[j])) || (atoi(gargv[j]) == code))
	  {
	    if (structured_output)
	      fprintf (stdout,"%d:%d:",code,len);
	    
	    fwrite (msg+i-len,1,len,stdout);
	      
	    if (structured_output)
	      fprintf (stdout,"\n");
	    
	    fflush (stdout);
	    fprintf (stderr,"\n");
	    fflush (stderr);
	    
	  }
      
    }
  return 0;
}



int  dhcpinform (int s, unsigned int ipaddr, unsigned int broadcast, unsigned char *ether)
{
  unsigned char msg[2048], magic_cookie[] = {99,130,83,99},
    msg_inform[] = {53,1,8}, msg_wpad[] = {252,0}, msg_ml_h[] = {57,02 }  ;
    
  struct timeval tv;
  unsigned int xid;
  unsigned short ml;
  struct sockaddr_in in,msgr;
  struct pollfd pfd;
  int sl, rsock,len, eom,j,code, pollto;
  
  /* setup listener */
  rsock = bind_r (ipaddr);
  if (rsock < 0)
    bail ("bind listener - probably a permissions issue");
      
  
  gettimeofday (&tv,NULL);
  xid = tv.tv_usec & 0xffffffff;
  ml = 2048;
  ml = htons(ml);
  
  memset (msg,0,2048);		/* set options full of PAD */
  
  msg[F_OP] =  1;		/* boot request */
  msg[F_HTYPE] = 1;		/* ethernet */
  msg[F_HLEN] = 6;		/* mac address length */
  msg[F_HOPS] = 0;		/* no relays yet */

  memcpy (msg+F_XID,&xid,4);	/* transaction id */
  
  memset (msg+F_SECS,0,2);	/* seconds since start */
  memset (msg+F_FLAGS,0,2);	/* we can accept unicast responses */

  memcpy (msg+F_CIADDR,&ipaddr,4); /* my confirmed IP */
  memset (msg+F_YIADDR,0,4);	/* offered IP */
  memset (msg+F_SIADDR,0,4);	/* dhcp server IP */
  memset (msg+F_GIADDR,0,4);	/* relay agent IP */
  
  memset (msg+F_CHADDR,0,16);
  memcpy (msg+F_CHADDR,ether,6); /* client hardware address */

  memset (msg+F_SNAME,0,64);
  memset (msg+F_FILE,0,128);
  
  memcpy (msg+F_COOKIE, magic_cookie,4); /* magic cookie  */
  
  memcpy (msg+F_INFORM, msg_inform, 3);	/* operation DHCPINFORM */
  
  memcpy (msg+F_WPAD, msg_wpad, 2); /* we'd like the WPAD string */
  
  memcpy (msg+F_ML_H, msg_ml_h,2); /* max length header */
  memcpy (msg+F_ML_D, &ml, 2);	/* max length data */
  
  eom = F_CUSTOM;

  for (code=1;code<255;code++)
    {				/* rfc 2131 says we should include all the options we care
				   about (that's all of them) in the request */
      if ((code != 252) && (code != 57) && (code != 53))
	{
	  msg[eom++] = code;
	  msg[eom++] = 0;
	}
    }
  
  
  msg[eom++] = 255;		/* end */
  
  /* ------------------- PACKET IS NOW SETUP  ---------------- */
  
  in.sin_family = AF_INET;
  in.sin_port = htons (67);
  memcpy (&in.sin_addr, &broadcast,sizeof (in.sin_addr));

  sl = sendto(s, msg, eom,
	      0,(void *) &in,sizeof (struct sockaddr_in));
  
  if (sl <eom)
    {
      perror ("sendto fialed");  
      bail ("sendto");
    }
  
  /* ---------------- DHCPINFORM HAS BEEN SENT -------------- */

  pfd.fd = rsock;
  pfd.events = POLLIN;
  
  pollto = 750;		/* we'll start the timeout at 750 ms, but drop it to 250 after */
  
  while (poll (&pfd,1,pollto))
    {				/* we loop here because there could be multiple dhcp severs
				   with different information that will respond to our broadcast */

      memset (msg,0,2048);		/* set options full of PAD */
      
      sl = sizeof(struct sockaddr_in);
      len = recvfrom (rsock, msg, 2048, 0,(struct sockaddr *) &msgr,&sl);
      
      fprintf (stderr,"%d bytes recevd\n",len);
      display_options (msg,len,magic_cookie);
      
      pfd.revents = 0;
      pollto = 250;
      if (!multiple_servers)
	break;
    }

  close (rsock);
  return 0;
}

void show_help()
{
  fprintf (stderr,"%s [-v] [-a] [-h] [-s] [option] [option2] [option3] ...\n",gargv[0]);
  fprintf (stderr,"\t-h : This help message and exit\n");
  fprintf (stderr,"\t-v : Context information on stderr\n");
  fprintf (stderr,"\t-m : Gather values from multiple servers (default is first to answer)\n");
  fprintf (stderr,"\t-a : print values on stdout for all recvd options (need -v for sanity.)\n");
  fprintf (stderr,"\t-s : print values for matched options in structured format(*) instead of plain value\n");
  fprintf (stderr,"\toption : DHCP option number (rfc 2132) to print value of on stdout\n");
  fprintf (stderr,"\t         keywords wpad (252), type (53), server (54), subnet (1), \n");
  fprintf (stderr,"\t         router (3), dns (6), and domain (15) are also valid options\n");

  fprintf (stderr,"\n(*) Structured output is code:length:data[CR]\n");

  fprintf (stderr,"\nValues printed to stdout are not converted to ascii, nor are newlines added.\n");
  
  
  return;
}

char *map_switch (char *o)
{
  char *rv;
  
  rv = o;
  
  if (!strcasecmp (o,"wpad"))
    rv = "252";
  else
  if (!strcasecmp (o,"type"))
    rv = "53";
  else
  if (!strcasecmp (o,"server"))
    rv = "54";
  else
  if (!strcasecmp (o,"subnet"))
    rv = "1";
  else
  if (!strcasecmp (o,"router"))
    rv = "3";
  else
  if (!strcasecmp (o,"dns"))
    rv = "6";
  else
  if (!strcasecmp (o,"domain"))
    rv = "15";


  return rv;
}


int main (int argc, char **argv)
{
  int fd,err,n,lfd,t1,verbose,i;
  struct ifreq ifr, *pifr;
  struct ifconf ifc;
  unsigned char ether[6];
  unsigned int ipaddr, broadcast;
  struct sockaddr_in *x, *y;
  
  verbose = 0;
  gargc = argc;
  gargv = argv;
  multiple_servers = 0;
  
  for (i=1;i<argc;i++)
    {
      if (!strcmp ("-v",argv[i]))
	verbose =1;
      if (!strcmp ("-s",argv[i]))
	structured_output =1;
      if (!strcmp ("-m",argv[i]))
	multiple_servers =1;

      if (!strcmp ("-h",argv[i]))
	{
	  show_help();
	  return 0;
	}
      argv[i] = map_switch (argv[i]);
    }
  
  if (!verbose)
    fclose (stderr);


  fd = socket (PF_INET, SOCK_DGRAM,0);
  
  n = 0;
  ifc.ifc_buf = NULL;
  
  do				/* there is no way to know the interface count before hand,
				 so we need to loop again and again upping our max each time
				until returned < max */
    {
      n += 32;
      if (ifc.ifc_buf)
	free (ifc.ifc_buf);
      ifc.ifc_buf = malloc (ifc.ifc_len=(sizeof(struct ifreq)*n));
      if (ioctl (fd,SIOCGIFCONF, &ifc) < 0)
	bail ("ioctl ifconf");
    }
  while (ifc.ifc_len == (sizeof (struct ifreq) * n));
  
  pifr= ifc.ifc_req;
  
  
  for (n = 0; n < ifc.ifc_len; (n += sizeof(struct ifreq)), pifr++) 
    {

      x = (void *)  &(pifr->ifr_addr);
      memcpy (&ipaddr,&(x->sin_addr),4);
      
      strncpy (ifr.ifr_name,pifr->ifr_name,IFNAMSIZ);
      if (!strstr (ifr.ifr_name,"eth"))
	continue;
      
      if (err = ioctl(fd,   SIOCGIFFLAGS,  &ifr))
	bail ("ioctl get flags");
      
      if ((ifr.ifr_flags & IFF_UP) && (ifr.ifr_flags & IFF_BROADCAST))
	{
	  if (err = ioctl(fd, SIOCGIFHWADDR, &ifr))
	    bail ("ioctl get mac");
	  memcpy (ether, ((struct sockaddr)(ifr.ifr_hwaddr)).sa_data,6);
	  
	  if (ioctl(fd, SIOCGIFBRDADDR, &ifr))
	    bail ("ioctl broadcast");
	
	  y = (struct sockaddr_in *)&ifr.ifr_broadaddr;
	  memcpy (&broadcast, &(y->sin_addr),4);

	  fprintf (stderr,"%s \tip=%08X \tbroadcast=%08X \t%02X:%02X:%02X:%02X:%02X:%02X\n",pifr->ifr_name,
		  ipaddr,broadcast,
		  ether[0],ether[1],ether[2],ether[3],ether[4],ether[5]   );  
	  
	  lfd = socket (PF_INET, SOCK_DGRAM,0);
	  if (bind (lfd,(struct sockaddr *) x,sizeof (struct sockaddr_in)))
	    bail ("bind");
	  t1 = 1;
	  if (setsockopt (lfd, SOL_SOCKET, SO_BROADCAST, (void *) &t1, sizeof (int)))
	    {
	      perror ("setsockopt broadcast on");
	      bail ("setsockopt broadcast on");
	    }
	  
	      
	  dhcpinform (lfd,ipaddr,broadcast,ether);
	  
	  close (lfd);
	}
      
  }
  close (fd);
  
  exit (0);
  
}

  
