/**********************************************************************
 *               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.                     *
 **********************************************************************/


#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <sys/time.h>

#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <net/if_arp.h>

#include <string.h>
#include <errno.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_OPERATION 240
#define F_ADDR      243
#define F_OPTIONLIST 249
#define F_EOM 252


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

int dhcpmsg (int s, unsigned int ipaddr,  unsigned char *ether)
{
    unsigned char msg[2048], 
        magic_cookie[] = {99,130,83,99},
        msg_operation[] = {53,1,3},
            msg_addr[] = {50,4,0,0,0,0},
            msg_router_optionlist[] = {55,1,3};
            
       struct timeval tv;
        unsigned int xid;
        unsigned short ml;
        struct sockaddr_in in,msgr;
        struct pollfd pfd;
        int len,i;
        socklen_t sl;

        memcpy (msg_addr+2, &ipaddr, 4);
        
        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);
        //        memset (msg+F_CIADDR,0,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_OPERATION, msg_operation, 3);	/* operation  */
        memcpy (msg+F_ADDR, msg_addr, 6);
        memcpy (msg+F_OPTIONLIST, msg_router_optionlist, 3); /* tell me about routers */
        msg[F_EOM] = 255;                                    /* end */

  
        /* ------------------- PACKET IS NOW SETUP  ---------------- */
  
        in.sin_family = AF_INET;
        in.sin_port = htons (67);
        in.sin_addr.s_addr = 0xffffffff;

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

        pfd.fd = s;
        pfd.events = POLLIN;
  
        while (poll (&pfd,1,750))
        {            
            unsigned int rxid;
            int code, olen;
            
            memset (msg,0,2048);		/* set options full of PAD */
            sl = sizeof(struct sockaddr_in);
            len = recvfrom (pfd.fd, msg, 2048, 0,(struct sockaddr *) &msgr,&sl);
            
            memcpy (&rxid, msg+F_XID, 4);
            if (xid != rxid) continue;            /* wrong xid - not for me */
            if (memcmp (magic_cookie,msg+F_OPTIONS,4)) continue;
            
            for (i=F_OPTIONS+4;i<len;)
            {
                code= msg[i];
                if ( (code == 255) || (code == 0))
                {
                    olen = 0;
                    i++;
                }
                else
                {
                    olen=msg[i+1];
                    i += 2+olen;
                }
      
                if (code == 255)		/* end of options */
                    break;
                
                if (code == 3 && olen == 4)
                {
                    struct in_addr router;
                    memcpy (&router.s_addr, msg + i - 4, 4);
                    fprintf (stderr, "Your Gateway is %s\n", inet_ntoa(router));
                    break;
                }
            }
            break;
        }
        return 0;
}

int main (int argc, char **argv)
{
    int fd,err,n,lfd;
    struct ifreq *pifr;
    struct ifconf ifc;
    unsigned char ether[6];
    unsigned int ipaddr, broadcast;
    int opton = 1;
    struct sockaddr_in localaddr;
    
    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 += 128;
        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 /sizeof (struct ifreq)); n++, pifr++)
    {
        if (pifr->ifr_addr.sa_family != AF_INET) continue; /* IPv4 */

        ipaddr = ((struct sockaddr_in *) &pifr->ifr_addr)->sin_addr.s_addr;
        
        if ((err = ioctl(fd,   SIOCGIFFLAGS,  pifr)))  bail ("ioctl get flags");
      
        if ((pifr->ifr_flags & IFF_UP) && (pifr->ifr_flags & IFF_BROADCAST))
	{
            if ((err = ioctl(fd, SIOCGIFHWADDR, pifr)))    bail ("ioctl get mac");
            if (pifr->ifr_hwaddr.sa_family != ARPHRD_ETHER) continue; /* not ethernet */
            memcpy (ether, ((struct sockaddr)(pifr->ifr_hwaddr)).sa_data,6);
            
            if (ioctl(fd, SIOCGIFBRDADDR, pifr))  bail ("ioctl broadcast");
	
            broadcast = ((struct sockaddr_in *) &pifr->ifr_broadaddr)->sin_addr.s_addr;
          
            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);
            localaddr.sin_family = PF_INET;
            localaddr.sin_port = htons(68);
            localaddr.sin_addr.s_addr = ipaddr;
            
            setsockopt(lfd,SOL_SOCKET, SO_REUSEADDR, &opton, sizeof(int));
            if (bind (lfd,(struct sockaddr *) &localaddr,sizeof (struct sockaddr_in)))   bail ("bind");
            if (setsockopt (lfd, SOL_SOCKET, SO_BROADCAST, (void *) &opton, sizeof (int)))
	    {
                perror ("setsockopt broadcast on");
                bail ("setsockopt broadcast on");
	    }
	      
            dhcpmsg (lfd,ipaddr,ether);
            close (lfd);
	}
    }
    close (fd);
    exit (0);
}
