/*

                                   _JNJ`                        
                               .JNMH`                           
                              JMMF`       `;.                   
                            .NMM)          `MN.                 
                            MMM)            (MML                
                           (MMM`             MMML               
                     M  I  D  N  I  G  H  T    C  o  D  E       
                           (NMMF             MHNH               
                            NMML            .MMM                
                             NMML          .NMH                 
                              4MMNL       .#F                   
                               `4HNNL_   `                      
                                   `"""`                        

                          Copyright (C) 2004-2007
       "Ian (Larry) Latter" <ian dot latter at midnightcode dot org>

    Midnight Code is a registered trademark of Ian Latter.

    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, and mirrored at the Midnight Code web
    site; as at version 2 of the License only.
  
    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.
  
    You should have received a copy of the GNU General Public License
    (version 2) along with this program; if not, write to the Free
    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
    02111-1307 USA, or see http://midnightcode.org/gplv2.txt

*/

/* midnightcode-bootp.c :: Midnight Code Bootp/Dhcp library		*/

/* Includes software to support RFCs :: 951, 1048, 1395, 1497, 1531, 1532, 1533, 1534, 1542 */


#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <linux/if.h>
#include <arpa/inet.h>
#include <errno.h>


#include "preferences.h"
#include "midnightcode-list.h"
#include "midnightcode-parse.h"
#include "midnightcode-config.h"
#include "midnightcode-interface.h"
#include "midnightcode-socket.h"
#include "midnightcode-ip.h"
#include "midnightcode-bootp.h"



int midnightcode_bootp_option_l[256] = {
  0, 4, 4, 1, 2, 2, 2, 1, 1, 1, 1, 1, 32, 2, 32, 32,
  4, 32, 32, 1, 1, 4, 2, 1, 4, 4, 2, 1, 4, 1, 1, 1,
  4, 4, 1, 4, 1, 1, 4, 1, 32, 2, 2, 32, 2, 2, 1, 4,
  2, 2, 4, 4, 1, 1, 4, 16, 4, 2, 4, 4, 32, 32, 7, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

int midnightcode_bootp_multiplier_l[256] = {
  1, 1, 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 1, 1, 1, 1,
  1, 1, 1, 1, 1, 8, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1,
  1, 8, 1, 1, 1, 1, 1, 1, 1, 4, 4, 1, 4, 4, 1, 1,
  4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
};



int
midnightcode_bootp_write_option(unsigned char ** option_up, int option,
                  int * buffer_max_p, void * value_p, int value_len) {
  unsigned char * option_p;
  int buffer_max;


  if(!option_up)
    return -1;
  if(option >= 128 && option <= 254)
    return -1;
  if(!buffer_max_p)
    return -1;
  if(value_len < 0)
    return -1;

  option_p = *option_up;
  if(!option_p)
    return -1;
  buffer_max = *buffer_max_p;
  if(buffer_max <= 0)
    return -1;

  if((buffer_max - ((midnightcode_bootp_option_l[option] *
    midnightcode_bootp_multiplier_l[option]) + 2)) < 0)
    return -1;
  if(value_len > (midnightcode_bootp_option_l[option] *
    midnightcode_bootp_multiplier_l[option]))
    return -1;

  *option_p = option;
  option_p++;
  if(value_len) {
    *option_p = value_len;
  } else {
    *option_p = midnightcode_bootp_option_l[option] *
      midnightcode_bootp_multiplier_l[option];
  }
  option_p++;
  if(value_p && value_len)
    memcpy(option_p, value_p, value_len);
  if(value_len) {
    option_p += value_len;
    buffer_max -= (value_len + 2);
  } else {
    option_p += (midnightcode_bootp_option_l[option] *
      midnightcode_bootp_multiplier_l[option]);
    buffer_max -= ((midnightcode_bootp_option_l[option] *
      midnightcode_bootp_multiplier_l[option]) + 2);
  }

  *option_up = option_p;
  *buffer_max_p = buffer_max;


  return 0;
}



/* TODO: pass resolver, pass routes */
int
midnightcode_bootp_read_option(unsigned char ** option_up,
                  struct midnightcode_interface_element_t ** interface_p,
                  char * buffer_max_p) {
  unsigned char * option_p;
  unsigned short * ushort_p;
  unsigned int * ulong_p;
  unsigned int ulong;
  unsigned int option;
  unsigned int option_size;
  int ret_i;


  if(!option_up)
    return -1;
  if(!interface_p)
    return -1;
  if(!*interface_p)
    return -1;
  if(!buffer_max_p)
    return -1;

  option_p = *option_up;
  if(!option_p)
    return -1;

  ret_i = 0;
  option = *option_p;

  /* TODO: consider single function / macro to pull in bootp op data */
  switch(option) {

    case MIDNIGHTCODE_BOOTP_OPTION_PAD:
    case MIDNIGHTCODE_BOOTP_OPTION_END:
      break;

    case MIDNIGHTCODE_BOOTP_OPTION_SUBNETMASK:
      ret_i = 1;
      option_p++;
      option_size = *option_p;
      if(option_p >= (unsigned char *)(buffer_max_p - option_size - 1))
        break;
      option_p++;
      memcpy(&(*interface_p)->bootp_ipv4_mask.s_addr, option_p, 4);
      inet_ntop(AF_INET, &(*interface_p)->bootp_ipv4_mask,
        (*interface_p)->bootp_ipv4_mask_s, 31);
      option_p += option_size - 1;
      break;

    case MIDNIGHTCODE_BOOTP_OPTION_GATEWAY:
      ret_i = 1;
      option_p++;
      option_size = *option_p;
      if(option_p >= (unsigned char *)(buffer_max_p - option_size - 1))
        break;
      option_p += option_size;
      break;

    case MIDNIGHTCODE_BOOTP_OPTION_DOMAINNAMESERVER:
      ret_i = 1;
      option_p++;
      option_size = *option_p;
      if(option_p >= (unsigned char *)(buffer_max_p - option_size - 1))
        break;
      option_p += option_size;
      break;

    case MIDNIGHTCODE_BOOTP_OPTION_HOSTNAME:
      ret_i = 1;
      option_p++;
      option_size = *option_p;
      if(option_p >= (unsigned char *)(buffer_max_p - option_size - 1))
        break;
      option_p++;
      memcpy(&(*interface_p)->hostname, option_p,
        sizeof((*interface_p)->hostname));
      option_p += option_size - 1;
      break;

    case MIDNIGHTCODE_BOOTP_OPTION_DOMAINNAME:
      ret_i = 1;
      option_p++;
      option_size = *option_p;
      if(option_p >= (unsigned char *)(buffer_max_p - option_size - 1))
        break;
      option_p += option_size;
      break;

    case MIDNIGHTCODE_BOOTP_OPTION_MTU:
      ret_i = 1;
      option_p++;
      option_size = *option_p;
      if(option_p >= (unsigned char *)(buffer_max_p - option_size - 1))
        break;
      option_p++;
      ushort_p = (unsigned short *)option_p;
      (*interface_p)->bootp_mtu = ntohs(*ushort_p);
      option_p += option_size - 1;
      break;

    case MIDNIGHTCODE_BOOTP_OPTION_BROADCASTADDRESS:
      ret_i = 1;
      option_p++;
      option_size = *option_p;
      if(option_p >= (unsigned char *)(buffer_max_p - option_size - 1))
        break;
      option_p++;
      memcpy(&(*interface_p)->bootp_ipv4_broadcast.s_addr, option_p, 4);
      inet_ntop(AF_INET, &(*interface_p)->bootp_ipv4_broadcast,
        (*interface_p)->bootp_ipv4_broadcast_s, 31);
      option_p += option_size - 1;
      break;

    case MIDNIGHTCODE_BOOTP_OPTION_DHCPSERVERID:
      ret_i = 1;
      option_p++;
      option_size = *option_p;
      if(option_p >= (unsigned char *)(buffer_max_p - option_size - 1))
        break;
      option_p++;
      memcpy(&(*interface_p)->bootp_ipv4_serverid.s_addr, option_p, 4);
      inet_ntop(AF_INET, &(*interface_p)->bootp_ipv4_serverid,
        (*interface_p)->bootp_ipv4_serverid_s, 31);
      option_p += option_size - 1;
      break;

    case MIDNIGHTCODE_BOOTP_OPTION_DHCPLEASETIME:
    case MIDNIGHTCODE_BOOTP_OPTION_DHCPRENEWALTIME:
    case MIDNIGHTCODE_BOOTP_OPTION_DHCPREBINDINGTIME:
      ret_i = 1;
      option_p++;
      option_size = *option_p;
      if(option_p >= (unsigned char *)(buffer_max_p - option_size - 1))
        break;
      option_p++;
      ulong_p = (unsigned int *)option_p;
      ulong = ntohl(*ulong_p);
      if((*interface_p)->bootp_renew > 0) {
        if(ulong > 0) {
          if((*interface_p)->bootp_renew > ulong)
            (*interface_p)->bootp_renew = ulong;
        }
      } else {
        (*interface_p)->bootp_renew = ulong;
      }
      /* TODO: Consider practical implementation of these options */
      /*
      if(option == MIDNIGHTCODE_BOOTP_OPTION_DHCPLEASETIME)
        (*interface_p)->bootp_renew = ntohl(*ulong_p);
      if(option == MIDNIGHTCODE_BOOTP_OPTION_DHCPRENEWALTIME)
        (*interface_p)->bootp_renew = ntohl(*ulong_p);
      if(option == MIDNIGHTCODE_BOOTP_OPTION_DHCPREBINDINGTIME)
        (*interface_p)->bootp_rebind = ntohl(*ulong_p);
      */
      option_p += option_size - 1;
      break;

    default:
      option_p++;
      option_size = *option_p;
      if(option_p >= (unsigned char *)(buffer_max_p - option_size - 1))
        break;
      option_p += option_size;
      break;

  }

  *option_up = option_p;

  return 0;
}



int
midnightcode_bootp_open(
                  struct midnightcode_interface_element_t ** interface_p,
                  struct midnightcode_socket_element_t ** socket_p) {
  struct midnightcode_socket_element_t * socket;


  if(!interface_p)
    return -1;
  if(!socket_p)
    return -1;


  *socket_p = NULL;
  if(midnightcode_socket_open(&socket,
    MIDNIGHTCODE_SOCKET_CONTROL_RAW, 0, 1,
    MIDNIGHTCODE_SOCKET_DIR_LISTEN, MIDNIGHTCODE_SOCKET_PROTOCOL_UDP,
    (*interface_p)->ipv4_address_s, MIDNIGHTCODE_BOOTP_CLIENT_PORT,
    (*interface_p)->name) < 0) {
    return -1;
  }
  if(midnightcode_socket_toggle_broadcast(socket, 1) < 0)
    return -1;

  memset(&socket->ipv4_remote, 0, sizeof(struct in_addr));
  socket->ipv4_remote.s_addr = INADDR_BROADCAST;
  snprintf(socket->ipv4_remote_s, 32, "%s", inet_ntoa(socket->ipv4_remote));
  socket->port_remote = MIDNIGHTCODE_BOOTP_SERVER_PORT;

  *socket_p = socket;


  return 0;
}



int
midnightcode_bootp_write(int dhcp_message,
                  struct midnightcode_interface_element_t ** interface_p,
                  struct midnightcode_socket_element_t * socket) {
  struct midnightcode_bootp_message_t * bootp;
  struct sockaddr_in sockaddr;
  int sockaddr_len;
  unsigned char option_magic[] = {99,130,83,99};
  unsigned char option_request[] = {
    MIDNIGHTCODE_BOOTP_OPTION_SUBNETMASK,
    MIDNIGHTCODE_BOOTP_OPTION_BROADCASTADDRESS,
    MIDNIGHTCODE_BOOTP_OPTION_MTU,
    MIDNIGHTCODE_BOOTP_OPTION_GATEWAY,
    MIDNIGHTCODE_BOOTP_OPTION_STATICROUTE,
    MIDNIGHTCODE_BOOTP_OPTION_HOSTNAME,
    MIDNIGHTCODE_BOOTP_OPTION_NAMESERVER,
    MIDNIGHTCODE_BOOTP_OPTION_TIMEOFFSET,
    MIDNIGHTCODE_BOOTP_OPTION_TIMESERVER};
  unsigned char option_client[1 + IFHWADDRLEN];
  unsigned char * option_p;
  char * bootp_p;
  int options_max;


  if(dhcp_message != MIDNIGHTCODE_BOOTP_DHCP_DISCOVER &&
    dhcp_message != MIDNIGHTCODE_BOOTP_DHCP_REQUEST &&
    dhcp_message != MIDNIGHTCODE_BOOTP_DHCP_DECLINE &&
    dhcp_message != MIDNIGHTCODE_BOOTP_DHCP_RELEASE) 
    return -1;
  if(!interface_p)
    return -1;
  if(!socket)
    return -1;


  bootp = malloc(sizeof(struct midnightcode_bootp_message_t));
  if(!bootp)
    return -1;
  memset(bootp, 0, sizeof(struct midnightcode_bootp_message_t));

  memset(&sockaddr, 0, sizeof(struct sockaddr_in));
  sockaddr.sin_family = AF_INET;
  sockaddr.sin_addr.s_addr = htonl(socket->ipv4_remote.s_addr);
  sockaddr.sin_port = htons(socket->port_remote);
  sockaddr_len = sizeof(struct sockaddr_in);

  if((*interface_p)->bootp_xid == 0  ||
    dhcp_message == MIDNIGHTCODE_BOOTP_DHCP_DISCOVER) {
    /* TODO: Randomly assign xid */
    if(-1 == -1) {
      (*interface_p)->bootp_xid = MIDNIGHTCODE_BOOTP_CLIENT_XID;
    }
  }

  /* NOTE: RFC951 Bootp */
  options_max = 312;
  bootp->op = MIDNIGHTCODE_BOOTP_OPCODE_BOOTREQUEST;
  bootp->htype = 1;
  bootp->hlen = IFHWADDRLEN;
  bootp->xid = htons((*interface_p)->bootp_xid);
  bootp->secs = 0;
  bootp->flags = 0;
  if(socket->ipv4_local.s_addr == INADDR_ANY)
    bootp->flags = htons(0x8000);
  memcpy(&(bootp->ciaddr), &(socket->ipv4_local.s_addr), 4);
  if(dhcp_message == MIDNIGHTCODE_BOOTP_DHCP_REQUEST ||
    dhcp_message == MIDNIGHTCODE_BOOTP_DHCP_RELEASE)
    memcpy(&(bootp->siaddr), &((*interface_p)->bootp_ipv4_server.s_addr), 4);
  memcpy(&bootp->chaddr, &(*interface_p)->mac_address, IFHWADDRLEN);
  memcpy(&bootp->options, &option_magic, 4); 

  option_p = (unsigned char *)&(bootp->options);
  option_p+= 4;
  options_max -= 4;

  /* NOTE: RFC1048+ Bootp Options */

  if(midnightcode_bootp_write_option(&option_p,
    MIDNIGHTCODE_BOOTP_OPTION_DHCPMESSAGETYPE, &options_max,
    &dhcp_message, 1) < 0) {
    free(bootp);
    return -1;
  }
  memset(&option_client, 0, sizeof(option_client));
  option_client[0] = 1;
  memcpy(&(option_client[1]), &(*interface_p)->mac_address, IFHWADDRLEN);
  if(midnightcode_bootp_write_option(&option_p,
    MIDNIGHTCODE_BOOTP_OPTION_DHCPCLIENTID, &options_max,
    &option_client, sizeof(option_client)) < 0) {
    free(bootp);
    return -1;
  }
  if(dhcp_message == MIDNIGHTCODE_BOOTP_DHCP_REQUEST ||
    dhcp_message == MIDNIGHTCODE_BOOTP_DHCP_RELEASE ) {
    if((*interface_p)->bootp_ipv4_serverid.s_addr != INADDR_ANY) {
      if(midnightcode_bootp_write_option(&option_p,
        MIDNIGHTCODE_BOOTP_OPTION_DHCPSERVERID, &options_max,
        &(*interface_p)->bootp_ipv4_serverid.s_addr, 4) < 0) {
        free(bootp);
        return -1;
      }
    }
    if((*interface_p)->bootp_ipv4_address.s_addr != INADDR_ANY) {
      if(midnightcode_bootp_write_option(&option_p,
        MIDNIGHTCODE_BOOTP_OPTION_DHCPREQUESTIP, &options_max,
        &(*interface_p)->bootp_ipv4_address.s_addr, 4) < 0) {
        free(bootp);
        return -1;
      }
    } else {
      if((*interface_p)->ipv4_address.s_addr != INADDR_ANY) {
        if(midnightcode_bootp_write_option(&option_p,
          MIDNIGHTCODE_BOOTP_OPTION_DHCPREQUESTIP, &options_max,
          &(*interface_p)->ipv4_address.s_addr, 4) < 0) {
          free(bootp);
          return -1;
        }
      } else {
        free(bootp);
        return -1;
      }
    }
  }
  if(dhcp_message == MIDNIGHTCODE_BOOTP_DHCP_DISCOVER ||
    dhcp_message == MIDNIGHTCODE_BOOTP_DHCP_REQUEST) {
    if(midnightcode_bootp_write_option(&option_p,
      MIDNIGHTCODE_BOOTP_OPTION_DHCPPARAMREQUEST, &options_max,
      &option_request, sizeof(option_request)) < 0) {
      free(bootp);
      return -1;
    } 
  }

  if((options_max - 1) < 0) {
    free(bootp);
    return -1;
  }
  options_max--;
  *option_p = MIDNIGHTCODE_BOOTP_OPTION_END;

  bootp_p = (char *)(bootp);
  if(sendto(socket->socket, bootp_p,
    sizeof(struct midnightcode_bootp_message_t), 0,
    (struct sockaddr *)&sockaddr, sockaddr_len) < 0) {
    free(bootp);
    return -1;
  }
  free(bootp);


  return 0;
}



int
midnightcode_bootp_read(int dhcp_message,
                  struct midnightcode_interface_element_t ** interface_p,
                  struct midnightcode_socket_element_t * socket) {
  struct midnightcode_bootp_message_t * bootp;
  char * buffer;
  int buffer_len;
  unsigned char option_magic[] = {99,130,83,99};
  unsigned char * option_p;
  int options_max;
  int message_type;


  if(dhcp_message != MIDNIGHTCODE_BOOTP_DHCP_OFFER &&
    dhcp_message != MIDNIGHTCODE_BOOTP_DHCP_ACK)
    return -1;
  if(!interface_p)
    return -1;
  if(!socket)
    return -1;


  buffer = NULL;
  buffer_len = 0;
  if(midnightcode_socket_read_block(socket, &buffer, &buffer_len, 1,
    MIDNIGHTCODE_BOOTP_CLIENT_TIMEOUT,
    MIDNIGHTCODE_SOCKET_IO_BLOCKING) <= 0) {
    return 0;
  }

  if(!buffer)
    return -1;
  if(buffer_len == 0)
    return -1;

  /* TODO: Loop and get another packet
     if packet_count > 0 && type != packet_type */

  bootp = (struct midnightcode_bootp_message_t *)buffer;
  if(ntohs(bootp->xid) != (*interface_p)->bootp_xid) {
    free(buffer);
    return -1;
  }
  if(bootp->op != MIDNIGHTCODE_BOOTP_OPCODE_BOOTREPLY) {
    free(buffer);
    return -1;
  }

  options_max = buffer_len - (sizeof(bootp->op) + sizeof(bootp->htype) +
    sizeof(bootp->hlen) + sizeof(bootp->hops) + sizeof(bootp->xid) +
    sizeof(bootp->secs) + sizeof(bootp->flags) + sizeof(bootp->ciaddr) +
    sizeof(bootp->yiaddr) + sizeof(bootp->siaddr) + sizeof(bootp->giaddr) +
    sizeof(bootp->chaddr[16]) + sizeof(bootp->sname[64]) +
    sizeof(bootp->file[128]));
  if(options_max < 0) {
    free(buffer);
    return -1;
  }

  if(options_max < 4) {
    /* NOTE: RFC951 Bootp */

    /* TODO: create bootp message_type, ignore retry loops in main routine */
    message_type = 0;

    memcpy(&((*interface_p)->bootp_ipv4_server.s_addr), &(bootp->siaddr), 4);
    inet_ntop(AF_INET, &(*interface_p)->bootp_ipv4_server,
      (*interface_p)->bootp_ipv4_server_s, 31);
    memcpy(&((*interface_p)->bootp_ipv4_address.s_addr), &(bootp->yiaddr), 4);
    inet_ntop(AF_INET, &(*interface_p)->bootp_ipv4_address,
      (*interface_p)->bootp_ipv4_address_s, 31);

  } else {
    /* NOTE: RFC1048+ Bootp Options */

    if(memcmp(&(bootp->options), &option_magic, 4) != 0) {
      free(buffer);
      return -1;
    }

    message_type = -1;
    for(option_p = ((unsigned char *)&(bootp->options) + 4);
      *option_p != MIDNIGHTCODE_BOOTP_OPTION_END &&
      option_p < (unsigned char *)&(buffer[buffer_len]) && message_type < 0;
      option_p++) {

      switch(*option_p) {

        case MIDNIGHTCODE_BOOTP_OPTION_PAD:
        case MIDNIGHTCODE_BOOTP_OPTION_END:
          break;

        case MIDNIGHTCODE_BOOTP_OPTION_DHCPMESSAGETYPE:
          option_p++;
          if(option_p >= (unsigned char *)(&(buffer[buffer_len]) - 1))
            break;
          option_p++;
          if(option_p >= (unsigned char *)(&(buffer[buffer_len]) -
            *option_p - 1))
            break;
          message_type = *option_p;
          option_p++;
          break;

        default:
          option_p++;
          if(option_p >= (unsigned char *)(&(buffer[buffer_len]) -
            *option_p - 1))
          option_p += *option_p + 1;
          break;

      }
    }
    if(message_type < MIDNIGHTCODE_BOOTP_DHCP_DISCOVER ||
      message_type > MIDNIGHTCODE_BOOTP_DHCP_RELEASE) {
      /* TODO: create bootp message_type, ignore retry loops in main routine */
      free(buffer);
      return -1;
    }

    if(dhcp_message != message_type)
      return 0;

    switch(message_type) {

      case MIDNIGHTCODE_BOOTP_DHCP_OFFER:
      case MIDNIGHTCODE_BOOTP_DHCP_ACK:
        memcpy(&((*interface_p)->bootp_ipv4_server.s_addr),
          &(bootp->siaddr), 4);
        inet_ntop(AF_INET, &(*interface_p)->bootp_ipv4_server,
          (*interface_p)->bootp_ipv4_server_s, 31);
        memcpy(&((*interface_p)->bootp_ipv4_address.s_addr),
          &(bootp->yiaddr), 4);
        inet_ntop(AF_INET, &(*interface_p)->bootp_ipv4_address,
          (*interface_p)->bootp_ipv4_address_s, 31);
        for(option_p = ((unsigned char *)&(bootp->options) + 4);
          *option_p != MIDNIGHTCODE_BOOTP_OPTION_END &&
          option_p < (unsigned char *)&(buffer[buffer_len]); option_p++) {
          if(midnightcode_bootp_read_option(&option_p, &(*interface_p),
            &(buffer[buffer_len])) < 0)
            break;
        }
        if(message_type == MIDNIGHTCODE_BOOTP_DHCP_ACK)
          (*interface_p)->bootp_xid = 0;
        break;

      case MIDNIGHTCODE_BOOTP_DHCP_NAK:
        (*interface_p)->bootp_xid = 0;
        break;

      default:
        message_type = 0;
        break;

    }
  }

  free(buffer);
  buffer = NULL;
  buffer_len = 0;


  return message_type;
}



int
midnightcode_bootp(int action,
                  struct midnightcode_interface_element_t ** interface) {
  struct midnightcode_socket_element_t * socket;
  int retries;
  int offer_retries;
  int request_retries;
  int retry_sleep;
  int skip_discover;
  int have_offer;
  int have_confirmed;
  int have_sent;
  int ret_i;


  if(action != MIDNIGHTCODE_BOOTP_ACTION_CONFIGURE &&
    action != MIDNIGHTCODE_BOOTP_ACTION_RELEASE)
    return -1;
  if(!interface)
    return -1;
  if(!*interface)
    return -1;
  if((*interface)->is_static)
    return 1;


  switch(action) {

    case MIDNIGHTCODE_BOOTP_ACTION_CONFIGURE:
      skip_discover = 0;
      (*interface)->bootp_mtu = (*interface)->mtu;
      (*interface)->bootp_ipv4_address.s_addr = INADDR_ANY;
      (*interface)->bootp_ipv4_mask.s_addr = INADDR_ANY;
      (*interface)->bootp_ipv4_broadcast.s_addr = INADDR_BROADCAST;
      inet_ntop(AF_INET, &(*interface)->bootp_ipv4_address,
        (*interface)->bootp_ipv4_address_s, 31);
      inet_ntop(AF_INET, &(*interface)->bootp_ipv4_mask,
        (*interface)->bootp_ipv4_mask_s, 31);
      inet_ntop(AF_INET, &(*interface)->bootp_ipv4_broadcast,
        (*interface)->bootp_ipv4_broadcast_s, 31);
      if((*interface)->ipv4_address.s_addr != INADDR_ANY &&
        (*interface)->bootp_ipv4_serverid.s_addr != INADDR_ANY)
        skip_discover = 1;
      retry_sleep = 0;
      for(retries = MIDNIGHTCODE_BOOTP_CLIENT_RETRIES;
        retries > 0; retries--) {
        retry_sleep += MIDNIGHTCODE_BOOTP_CLIENT_TIMEOUT;
        have_offer = 0;
        if(!skip_discover) {
          for(offer_retries = MIDNIGHTCODE_BOOTP_CLIENT_RETRIES_OFFER;
            offer_retries > 0; offer_retries--) {
            socket = NULL;
            if(midnightcode_bootp_open(interface, &socket) < 0) {
              sleep(retry_sleep);
              continue;
            }
            if(midnightcode_bootp_write(MIDNIGHTCODE_BOOTP_DHCP_DISCOVER,
              interface, socket) < 0) {
              midnightcode_socket_close(socket);
              sleep(retry_sleep);
              continue;
            }
            if(midnightcode_bootp_read(MIDNIGHTCODE_BOOTP_DHCP_OFFER,
              interface, socket) == MIDNIGHTCODE_BOOTP_DHCP_OFFER) {
              have_offer = 1;
              midnightcode_socket_close(socket);
              break;
            }
            midnightcode_socket_close(socket);
          }
        }
        if(!skip_discover && !have_offer)
          return 0;
        have_confirmed = 0;
        for(request_retries = MIDNIGHTCODE_BOOTP_CLIENT_RETRIES_REQUEST;
          request_retries > 0; request_retries--) {
          socket = NULL;
          if(midnightcode_bootp_open(interface, &socket) < 0) {
            sleep(retry_sleep);
            continue;
          }
          if(midnightcode_bootp_write(MIDNIGHTCODE_BOOTP_DHCP_REQUEST,
            interface, socket) < 0) {
            midnightcode_socket_close(socket);
            sleep(retry_sleep);
            continue;
          }
          if((ret_i = midnightcode_bootp_read(MIDNIGHTCODE_BOOTP_DHCP_ACK,
            interface, socket)) < 0) {
            midnightcode_socket_close(socket);
            continue;
          }
          if(ret_i == MIDNIGHTCODE_BOOTP_DHCP_ACK) {
            have_confirmed = 1;
            midnightcode_socket_close(socket);
            break;
          }
          if(ret_i == MIDNIGHTCODE_BOOTP_DHCP_NAK) {
            if(skip_discover)
              skip_discover = 0;
            midnightcode_socket_close(socket);
            break;
          }
          midnightcode_socket_close(socket);
        }
        if(have_confirmed)
          break;
      }
      if(!have_confirmed)
        return 0;
      (*interface)->mtu = (*interface)->bootp_mtu;
      (*interface)->ipv4_address = (*interface)->bootp_ipv4_address;
      if((*interface)->bootp_ipv4_mask.s_addr == INADDR_ANY) {
        midnightcode_ip_classmask(&((*interface)->ipv4_mask.s_addr),
          (*interface)->ipv4_address.s_addr);
      } else {
        (*interface)->ipv4_mask = (*interface)->bootp_ipv4_mask;
      }
      if((*interface)->bootp_ipv4_broadcast.s_addr == INADDR_BROADCAST) {
        midnightcode_ip_broadcast(&((*interface)->ipv4_broadcast.s_addr),
          (*interface)->ipv4_address.s_addr, (*interface)->ipv4_mask.s_addr);
      } else {
        (*interface)->ipv4_broadcast = (*interface)->bootp_ipv4_broadcast;
      }
      inet_ntop(AF_INET, &(*interface)->ipv4_address,
        (*interface)->ipv4_address_s, 31);
      inet_ntop(AF_INET, &(*interface)->ipv4_mask,
        (*interface)->ipv4_mask_s, 31);
      inet_ntop(AF_INET, &(*interface)->ipv4_broadcast,
        (*interface)->ipv4_broadcast_s, 31);
      break;

    case MIDNIGHTCODE_BOOTP_ACTION_RELEASE:
      socket = NULL;
      if((*interface)->ipv4_address.s_addr == INADDR_ANY ||
        (*interface)->bootp_ipv4_serverid.s_addr == INADDR_ANY)
        return 0;
      have_sent = 0;
      retry_sleep = 0;
      for(retries = MIDNIGHTCODE_BOOTP_CLIENT_RETRIES;
        retries > 0; retries--) {
        retry_sleep += MIDNIGHTCODE_BOOTP_CLIENT_TIMEOUT;
        if(midnightcode_bootp_open(interface, &socket) < 0) {
          sleep(retry_sleep);
          continue;
        }
        if(midnightcode_bootp_write(MIDNIGHTCODE_BOOTP_DHCP_RELEASE,
          interface, socket) == 0) {
          have_sent = 1;
          midnightcode_socket_close(socket);
          break;
        }
        midnightcode_socket_close(socket);
        sleep(retry_sleep);
      }
      if(!have_sent)
        return 0;
      break;

  }


  return 1;
}

// http://www.faqs.org/rfcs/rfc1531.html
