/*

                                   _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-route.c :: Midnight Code network route library		*/


#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <net/route.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <net/if.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <sys/socket.h>

#include "preferences.h"
#include "midnightcode-list.h"
#include "midnightcode-parse.h"
#include "midnightcode-config.h"
#include "midnightcode-ip.h"
#include "midnightcode-route.h"



char * midnightcode_route_scope_l[56] = {
  "site", "", "", "", "", "", "", "", "", "",
  "", "", "", "", "", "", "", "", "", "",
  "", "", "", "", "", "", "", "", "", "",
  "", "", "", "", "", "", "", "", "", "",
  "", "", "", "", "", "", "", "", "", "",
  "", "", "", "link", "host", "nowhere"
};



int
midnightcode_route_scope(char * buffer, int buffer_size, int scope) {
  int ret_i;


  if(!buffer)
    return -1;
  if(buffer_size <= 0)
    return -1;
  if(scope < 200 || scope > 255)
    return -1;

  snprintf(buffer, buffer_size - 1, "%s",
    midnightcode_route_scope_l[(scope - 200)]);
  ret_i = strlen(midnightcode_route_scope_l[(scope - 200)]);

  return ret_i;
}



int
midnightcode_route_listrecv(int sockfd, char *msgbuf, int bufsize,
                            uint msgseq, uint msgpid) {
  struct nlmsghdr *msg_nlhdr;
  int msglen, recvlen, term;


  term = 0;

  msglen = 0;
  do {
    if((recvlen = recv(sockfd, msgbuf, bufsize - msglen, 0)) < 0)
      return -1;

    msg_nlhdr = (struct nlmsghdr *)msgbuf;

    if(NLMSG_OK(msg_nlhdr, (uint)recvlen) == 0)
      return -1;

    if(msg_nlhdr->nlmsg_type == NLMSG_ERROR) {
      return -1;
    }

    if (msg_nlhdr->nlmsg_type == NLMSG_DONE) {
      term++;
      break;
    } else {
      msgbuf += recvlen;
      msglen += recvlen;
    }

    if((msg_nlhdr->nlmsg_flags & NLM_F_MULTI) == 0) {
      term++;
      break;
    }

  } while ((msg_nlhdr->nlmsg_seq != msgseq) ||
    (msg_nlhdr->nlmsg_pid != msgpid) || (term == 0));

  return msglen;
}



int
midnightcode_route_listadd(struct midnightcode_route_head_t * elements,
                           struct midnightcode_route_element_t * now_element,
                           void * void_nlhdr) {
  struct nlmsghdr *msg_nlhdr;
  struct rtmsg *msg_rt;
  struct rtattr *msg_rtattr;
  int msgrtlen;
  struct in_addr any;


  memset(&any, 0, sizeof(struct in_addr));
  any.s_addr = htonl(INADDR_ANY);

  msg_nlhdr = void_nlhdr;
  msg_rt = (struct rtmsg *)NLMSG_DATA(msg_nlhdr);
  msg_rtattr = (struct rtattr *)RTM_RTA(msg_rt);
  msgrtlen = RTM_PAYLOAD(msg_nlhdr);

  now_element = malloc(sizeof(struct midnightcode_route_element_t));
  memset(now_element, 0, sizeof(struct midnightcode_route_element_t));

  now_element->flags = msg_rt->rtm_flags;
  now_element->scope = msg_rt->rtm_scope;

  for(; RTA_OK(msg_rtattr, msgrtlen);
      msg_rtattr = RTA_NEXT(msg_rtattr, msgrtlen)) {

    switch(msg_rtattr->rta_type) {

      case RTA_DST:
        now_element->ipv4_destination = (*(struct in_addr *)
                                                    RTA_DATA(msg_rtattr));
	inet_ntop(AF_INET, RTA_DATA(msg_rtattr), 
                  now_element->ipv4_destination_s,
                  sizeof(now_element->ipv4_destination_s));
        midnightcode_ip_cidrtomask(&now_element->ipv4_destmask.s_addr,
          msg_rt->rtm_dst_len);
	inet_ntop(AF_INET, &now_element->ipv4_destmask.s_addr, 
                  now_element->ipv4_destmask_s,
                  sizeof(now_element->ipv4_destmask_s));
        break;

      case RTA_GATEWAY:
        now_element->ipv4_gateway = (*(struct in_addr *)RTA_DATA(msg_rtattr));
	inet_ntop(AF_INET, RTA_DATA(msg_rtattr), 
                  now_element->ipv4_gateway_s,
                  sizeof(now_element->ipv4_gateway_s));
        break;

      case RTA_IIF:
        now_element->in_ifindex = *(int *)RTA_DATA(msg_rtattr);
        /* TODO: set in_ifname */
        break;

      case RTA_METRICS:
        now_element->metric = *(int *)RTA_DATA(msg_rtattr);
        break;

      case RTA_OIF:
        now_element->out_ifindex = *(int *)RTA_DATA(msg_rtattr);
        /* TODO: set out_ifname */
        break;

      case RTA_PREFSRC:
        now_element->ipv4_prefsource = (*(struct in_addr *)
                                                   RTA_DATA(msg_rtattr));
	inet_ntop(AF_INET, RTA_DATA(msg_rtattr), 
                  now_element->ipv4_prefsource_s,
                  sizeof(now_element->ipv4_prefsource_s));
        break;

      case RTA_PRIORITY:
        now_element->priority = *(int *)RTA_DATA(msg_rtattr);
        break;

      case RTA_SRC:
        now_element->ipv4_source = (*(struct in_addr *)RTA_DATA(msg_rtattr));
	inet_ntop(AF_INET, RTA_DATA(msg_rtattr), 
                  now_element->ipv4_source_s,
                  sizeof(now_element->ipv4_source_s));
        midnightcode_ip_cidrtomask(&now_element->ipv4_sourcemask.s_addr,
          msg_rt->rtm_src_len);
	inet_ntop(AF_INET, &now_element->ipv4_sourcemask.s_addr, 
                  now_element->ipv4_sourcemask_s,
                  sizeof(now_element->ipv4_sourcemask_s));
        break;

      default:
        break;

    }

  }

  if(midnightcode_ip_cmp(&any, &(now_element->ipv4_destination)) == 0)
    snprintf(now_element->ipv4_destination_s,
      sizeof(now_element->ipv4_destination_s) - 1, "0.0.0.0");
  if(midnightcode_ip_cmp(&any, &(now_element->ipv4_destmask)) == 0)
    snprintf(now_element->ipv4_destmask_s,
      sizeof(now_element->ipv4_destmask_s) - 1, "0.0.0.0");
  if(midnightcode_ip_cmp(&any, &(now_element->ipv4_gateway)) == 0)
    snprintf(now_element->ipv4_gateway_s,
      sizeof(now_element->ipv4_gateway_s) - 1, "0.0.0.0");
  if(midnightcode_ip_cmp(&any, &(now_element->ipv4_prefsource)) == 0)
    snprintf(now_element->ipv4_prefsource_s,
      sizeof(now_element->ipv4_prefsource_s) - 1, "0.0.0.0");
  if(midnightcode_ip_cmp(&any, &(now_element->ipv4_source)) == 0)
    snprintf(now_element->ipv4_source_s,
      sizeof(now_element->ipv4_source_s) - 1, "0.0.0.0");

  if(midnightcode_ip_cmp(&any, &(now_element->ipv4_destination)) == 0 &&
    midnightcode_ip_cmp(&any, &(now_element->ipv4_destmask)) == 0 &&
    midnightcode_ip_cmp(&any, &(now_element->ipv4_prefsource)) == 0 &&
    midnightcode_ip_cmp(&any, &(now_element->ipv4_gateway)) != 0)
    now_element->is_defaultroute = 1;

  midnightcode_list_append(elements, now_element);

  return 1;
}



int
midnightcode_route_list(struct midnightcode_route_head_t * elements) {
  int sockfd;
  struct midnightcode_route_element_t * now_element;
  struct nlmsghdr *msg_nlhdr;
  struct rtmsg *msg_rt;
  char * msgbuf;
  int bufsize;
  int msgpid;
  int msgseq;
  int msglen;


  msgpid = (int)getpid();
  msgseq = 0;
  bufsize = 16384;

  msgbuf = malloc(bufsize);
  if(msgbuf == NULL)
    return -1;
  memset(msgbuf, 0, sizeof(msgbuf));

  if((sockfd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE)) < 0) {
    free(msgbuf);
    return -1;
  }

  msg_nlhdr = (struct nlmsghdr *)msgbuf;
  msg_nlhdr->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
  msg_nlhdr->nlmsg_type = RTM_GETROUTE;
  msg_nlhdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
  msg_nlhdr->nlmsg_seq = msgseq++;
  msg_nlhdr->nlmsg_pid = msgpid;

  msg_rt = (struct rtmsg *)NLMSG_DATA(msg_nlhdr);
  msg_rt->rtm_table = RT_TABLE_MAIN;
  msg_rt->rtm_protocol = RTPROT_UNSPEC;
  msg_rt->rtm_scope = RT_SCOPE_UNIVERSE;
  msg_rt->rtm_family = AF_INET;
  msg_rt->rtm_type = RTN_UNICAST;
  msg_rt->rtm_dst_len = 0;
  msg_rt->rtm_src_len = 0;

  if(send(sockfd, msg_nlhdr, msg_nlhdr->nlmsg_len, 0) < 0) {
    close(sockfd);
    free(msgbuf);
    return -1;
  }

  if((msglen = midnightcode_route_listrecv(sockfd, msgbuf, bufsize,
    msgseq, msgpid)) < 0) {
    close(sockfd);
    free(msgbuf);
    return -1;
  }

  now_element = NULL;
  for(; NLMSG_OK(msg_nlhdr, (uint)msglen);
    msg_nlhdr = NLMSG_NEXT(msg_nlhdr, msglen))
    midnightcode_route_listadd(elements, now_element, (void *)msg_nlhdr);

  close(sockfd);
  free(msgbuf);

  return 0;
}



int
midnightcode_route_default(struct midnightcode_route_head_t *elements) {
  struct midnightcode_route_element_t * now_element;
  struct midnightcode_route_element_t * old_element;


  if(midnightcode_route_list(elements) < 0)
    return -1;

  old_element = NULL;
  midnightcode_list_for_each(now_element, elements) {
    if(old_element != NULL) {
      free(old_element);
      old_element = NULL;
    }
    if(now_element->is_defaultroute != 1) {
      midnightcode_list_remove(elements, now_element);
      old_element = now_element;
    }
  }
  if(old_element != NULL)
    free(old_element);

  return 0;
}



int
midnightcode_route_add(char * ipv4_address_s, char * ipv4_mask_s,
                           char * ipv4_gateway_s, char * interface) {
  struct rtentry route;
  struct sockaddr_in addr;
  struct in_addr ipv4_address;
  struct in_addr ipv4_mask;
  struct in_addr ipv4_gateway;

  int sockfd;
  int one;


  if(!interface)
    return -1;
  if(!ipv4_address_s)
    return -1;
  if(!ipv4_mask_s)
    return -1;
  if(!ipv4_gateway_s)
    return -1;
  if(!interface)
    return -1;
  if(strlen(interface) > IFNAMSIZ)
    return -1;


  if((sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP)) < 0)
    return -1;

  one = 1;
  if(setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &one, sizeof(one))) {
    close(sockfd);
    return -1;
  }

  memset(&ipv4_address, 0, sizeof(struct in_addr));
  memset(&ipv4_mask, 0, sizeof(struct in_addr));
  memset(&ipv4_gateway, 0, sizeof(struct in_addr));
  if(midnightcode_parse_strtovar((void *)&ipv4_address,
    ipv4_address_s, 0, 0, MIDNIGHTCODE_PARSE_TYPE_IP)) {
    close(sockfd);
    return -1;
  }
  if(midnightcode_parse_strtovar((void *)&ipv4_mask,
    ipv4_mask_s, 0, 0, MIDNIGHTCODE_PARSE_TYPE_IP)) {
    close(sockfd);
    return -1;
  }
  if(midnightcode_parse_strtovar((void *)&ipv4_gateway,
    ipv4_gateway_s, 0, 0, MIDNIGHTCODE_PARSE_TYPE_IP)) {
    close(sockfd);
    return -1;
  }

  memset(&route, 0, sizeof(struct rtentry));
  memset(&addr, 0, sizeof(struct sockaddr_in));

  route.rt_dev = interface;
  route.rt_flags = RTF_UP|RTF_GATEWAY;
  route.rt_metric = 0;

  addr.sin_family = AF_INET;
  addr.sin_port = 0;
  addr.sin_addr = ipv4_address;
  memcpy(&route.rt_dst, &addr, sizeof(struct sockaddr_in));
  addr.sin_addr = ipv4_mask;
  memcpy(&route.rt_genmask, &addr, sizeof(struct sockaddr_in));
  addr.sin_addr = ipv4_gateway;
  memcpy(&route.rt_gateway, &addr, sizeof(struct sockaddr_in));

  if(ioctl(sockfd, SIOCADDRT, &route)) {
    close(sockfd);
    return -1;
  }
  close(sockfd);


  return 0;
}


/* TODO: midnightcode_route_find() .. find route for target IP */
/* TODO: midnightcode_route_find() .. note IP lib now exists for bin math */

int
midnightcode_route_to_config(
                     struct midnightcode_route_head_t * route_elements,
                     struct midnightcode_config_head_t * config_elements) {
  struct midnightcode_route_element_t * route_element;
  char section_name[64];
  int entry_count;


  if(!route_elements || !config_elements)
    return -1;

  if(midnightcode_config_del_structure(config_elements,
                                       MIDNIGHTCODE_ROUTE_STRUCTURE))
    return -1;

  entry_count = 0;
  midnightcode_list_for_each(route_element, route_elements) {

    if(route_element->scope != MIDNIGHTCODE_ROUTE_SCOPE_GLOBAL)
      continue;

    memset(&section_name, 0, sizeof(section_name));
    snprintf(section_name, sizeof(section_name) - 1,
      "static%d", entry_count);

    if(midnightcode_config_add_pair(config_elements,
                                    MIDNIGHTCODE_ROUTE_STRUCTURE,
                                    section_name,
                                    "ipv4_target",
                                    route_element->ipv4_destination_s,
                                    MIDNIGHTCODE_PARSE_TYPE_STRING))
      return -1;

    if(midnightcode_config_add_pair(config_elements,
                                    MIDNIGHTCODE_ROUTE_STRUCTURE,
                                    section_name,
                                    "ipv4_mask",
                                    route_element->ipv4_destmask_s,
                                    MIDNIGHTCODE_PARSE_TYPE_STRING))
      return -1;

    if(midnightcode_config_add_pair(config_elements,
                                    MIDNIGHTCODE_ROUTE_STRUCTURE,
                                    section_name,
                                    "ipv4_gateway",
                                    route_element->ipv4_gateway_s,
                                    MIDNIGHTCODE_PARSE_TYPE_STRING))
      return -1;

    if(midnightcode_config_add_pair(config_elements,
                                    MIDNIGHTCODE_ROUTE_STRUCTURE,
                                    section_name,
                                    "metric",
                                    &route_element->metric,
                                    MIDNIGHTCODE_PARSE_TYPE_INT))
      return -1;

    if(midnightcode_config_add_pair(config_elements,
                                    MIDNIGHTCODE_ROUTE_STRUCTURE,
                                    section_name,
                                    "interface",
                                    route_element->out_ifname,
                                    MIDNIGHTCODE_PARSE_TYPE_STRING))
      return -1;

    entry_count++;
  }


  return 0;
}



int
midnightcode_route_from_config(
                     struct midnightcode_route_head_t * route_elements,
                     struct midnightcode_config_head_t * config_elements) {
  struct midnightcode_route_element_t * route_element;
  struct midnightcode_config_section_element_t * section_element;
  struct midnightcode_config_section_element_t * old_element;
  struct midnightcode_config_section_head_t * section_elements;
  void * tmp_void_p;
  int ret_i;
  midnightcode_list_init(config_section_struct_head,
                         midnightcode_config_section_element_t);


  if(!route_elements || !config_elements)
    return -1;

  tmp_void_p = (void *)&config_section_struct_head;
  section_elements = tmp_void_p;

  if(!midnightcode_config_get_sections(config_elements,
                                       section_elements,
                                       MIDNIGHTCODE_ROUTE_STRUCTURE))
    return -1;

  ret_i = 0;
  old_element = NULL;
  midnightcode_list_for_each(section_element, section_elements) {

    route_element = malloc(
                          sizeof(struct midnightcode_route_element_t));
    if(!route_element)
      break;
    memset(route_element, 0,
      sizeof(struct midnightcode_route_element_t));

    ret_i = -1;
    if(midnightcode_config_get_pair(config_elements,
                                    MIDNIGHTCODE_ROUTE_STRUCTURE,
                                    section_element->section,
                                    "ipv4_target",
                                    &route_element->ipv4_destination, 0,
                                    MIDNIGHTCODE_PARSE_TYPE_IP))
      break;

    inet_ntop(AF_INET, &route_element->ipv4_destination,
      route_element->ipv4_destination_s, 31);

    if(midnightcode_config_get_pair(config_elements,
                                    MIDNIGHTCODE_ROUTE_STRUCTURE,
                                    section_element->section,
                                    "ipv4_mask",
                                    &route_element->ipv4_destmask, 0,
                                    MIDNIGHTCODE_PARSE_TYPE_IP))
      break;

    inet_ntop(AF_INET, &route_element->ipv4_destmask,
      route_element->ipv4_destmask_s, 31);

    if(midnightcode_config_get_pair(config_elements,
                                    MIDNIGHTCODE_ROUTE_STRUCTURE,
                                    section_element->section,
                                    "ipv4_gateway",
                                    &route_element->ipv4_gateway, 0,
                                    MIDNIGHTCODE_PARSE_TYPE_IP))
      break;

    inet_ntop(AF_INET, &route_element->ipv4_gateway,
      route_element->ipv4_gateway_s, 31);

    if(midnightcode_config_get_pair(config_elements,
                                    MIDNIGHTCODE_ROUTE_STRUCTURE,
                                    section_element->section,
                                    "metric",
                                    &route_element->metric, 0,
                                    MIDNIGHTCODE_PARSE_TYPE_INT))
      break;

    if(midnightcode_config_get_pair(config_elements,
                                    MIDNIGHTCODE_ROUTE_STRUCTURE,
                                    section_element->section,
                                    "interface",
                                    &route_element->out_ifname, 64,
                                    MIDNIGHTCODE_PARSE_TYPE_STRING))
      break;

    ret_i = 0;
    midnightcode_list_append(route_elements, route_element);

    old_element = section_element;
    midnightcode_list_remove(section_elements, section_element);
    if(old_element != NULL) {
      free(old_element);
      old_element = NULL;
    }

  }
  old_element = NULL;
  midnightcode_list_for_each(section_element, section_elements) {
    old_element = section_element;
    midnightcode_list_remove(section_elements, section_element);
    if(old_element != NULL) {
      free(old_element);
      old_element = NULL;
    }
  }


  return ret_i;
}


