/* script.c
 *
 * Functions to call the DHCP client notification scripts
 *
 * Russ Dill <Russ.Dill@asu.edu> July 2001
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "common.h"
#include "options.h"
#include "dhcpd.h"
#include "dhcpc.h"
#include "script.h"


#define CPE_VENDOR_CLASS_ID "mot_wmx_cpe"
#define MOT_WMX_VENDOR_CLASS "mot_wmx_"

/* Motorola Vendor Specific options */
#define EMS_FQDN      0x01
#define PROV_SVRS     0x02
#define LOCATION_SVRS 0x03
#define L2TP_CONFIG   0x04


#define L2TP_CONFIG_FILE			"l2tpv3"
#define TEMP_CPE_STATS_PATH  "/tmp/cpe/stats"
#define MAX_L2TP_MSG_SIZE			4000
#define MAX_L2TP_RULE_NUM			3
#define MAX_L2TP_RULE_VLAN_NUM 		3
#define MAX_L2TP_RULE_IPADDR_SIZE	17

/* Structure to store the L2TP rules */
struct l2tp_config_rules{
   unsigned char ipaddr[MAX_L2TP_RULE_IPADDR_SIZE]; // sizeof("255.255.255.255 ")
   unsigned long int session_id; //Do we need to use short int???
   unsigned int mtu_size;
   unsigned char vlan_num; //Do we need ot use char???
   unsigned int vlan_id[MAX_L2TP_RULE_VLAN_NUM];
   unsigned char cookie_len;
   unsigned char cookie[9];
};


/* get a rough idea of how long an option will be (rounding up...) */
static const int max_option_length[] = {
	[OPTION_IP] =		sizeof("255.255.255.255 "),
	[OPTION_IP_PAIR] =	sizeof("255.255.255.255 ") * 2,
	[OPTION_STRING] =	1,
	[OPTION_BOOLEAN] =	sizeof("yes "),
	[OPTION_U8] =		sizeof("255 "),
	[OPTION_U16] =		sizeof("65535 "),
	[OPTION_S16] =		sizeof("-32768 "),
	[OPTION_U32] =		sizeof("4294967295 "),
	[OPTION_S32] =		sizeof("-2147483684 "),
};


static inline int upper_length(int length, int opt_index)
{
	return max_option_length[opt_index] *
		(length / option_lengths[opt_index]);
}


static int sprintip(char *dest, char *pre, uint8_t *ip)
{
	return sprintf(dest, "%s%d.%d.%d.%d", pre, ip[0], ip[1], ip[2], ip[3]);
}


/* really simple implementation, just count the bits */
static int mton(struct in_addr *mask)
{
	int i;
	unsigned long bits = ntohl(mask->s_addr);
	/* too bad one can't check the carry bit, etc in c bit
	 * shifting */
	for (i = 0; i < 32 && !((bits >> i) & 1); i++);
	return 32 - i;
}


/* Fill dest with the text of option 'option'. */
static void fill_options(char *dest, uint8_t *option, struct dhcp_option *type_p)
{
	int type, optlen;
	uint16_t val_u16;
	int16_t val_s16;
	uint32_t val_u32;
	int32_t val_s32;
	int len = option[OPT_LEN - 2];

	dest += sprintf(dest, "%s=", type_p->name);

	type = type_p->flags & TYPE_MASK;
	optlen = option_lengths[type];
	for(;;) {
		switch (type) {
		case OPTION_IP_PAIR:
			dest += sprintip(dest, "", option);
			*(dest++) = '/';
			option += 4;
			optlen = 4;
		case OPTION_IP:	/* Works regardless of host byte order. */
			dest += sprintip(dest, "", option);
			break;
		case OPTION_BOOLEAN:
			dest += sprintf(dest, *option ? "yes" : "no");
			break;
		case OPTION_U8:
			dest += sprintf(dest, "%u", *option);
			break;
		case OPTION_U16:
			memcpy(&val_u16, option, 2);
			if (!strcmp(type_p->name, "mtu"))
			{
				dest += sprintf(dest, "%u", ((ntohs(val_u16) < 1300) || (ntohs(val_u16) > 1500)) ?
						1500 : ntohs(val_u16));
			}
			else
			{
			        dest += sprintf(dest, "%u", ntohs(val_u16));
			}
			break;
		case OPTION_S16:
			memcpy(&val_s16, option, 2);
			dest += sprintf(dest, "%d", ntohs(val_s16));
			break;
		case OPTION_U32:
			memcpy(&val_u32, option, 4);
			dest += sprintf(dest, "%lu", (unsigned long) ntohl(val_u32));
			break;
		case OPTION_S32:
			memcpy(&val_s32, option, 4);
			dest += sprintf(dest, "%ld", (long) ntohl(val_s32));
			break;
		case OPTION_STRING:
			memcpy(dest, option, len);
			dest[len] = '\0';
			return;	 /* Short circuit this case */
		}
		option += optlen;
		len -= optlen;
		if (len <= 0) break;
		dest += sprintf(dest, " ");
	}
}


/* L2TP-Rule-Checker
	1. Only one wildcard session (like 192.168.1.*)
	2. When wildcard session is configures, no other session shall be configured.
	3. Only one default session shall be configured (default session means vlanid is 0)
*/
#define L2TP_RULE_MAX_WILDCARD_SESSION_CNT	(1)
#define L2TP_RULE_MAX_DEFAULT_SESSION_CNT	(1)

static int isWildcardIPAddres( unsigned char *pIPAddrStr)
{
	int i;

	for( i=0; pIPAddrStr[i] && (i<MAX_L2TP_RULE_IPADDR_SIZE); i++)
	{
		if( '*' == pIPAddrStr[i])
			return TRUE;
	}

	return FALSE;
} /* END of isWildcardIPAddres() */

static int isValid_l2tp_conf(struct l2tp_config_rules *rules, int num)
{
	int i;
	int	nDefaultSession=0, nWildcardSession=0, nOtherSession=0;

	/* verify input args. */
    if (num>MAX_L2TP_RULE_NUM)
    	return FALSE;

    /* Collect session information at DCHP option */
    for(i=0; i<num; i++)
    {
		if ( isWildcardIPAddres( rules[i].ipaddr) )
			nWildcardSession++;
        else if ( 0 == rules[i].vlan_id[0])
            nDefaultSession++;
        else
        	nOtherSession++;
	}

	/* Check Rule 1, Only one wildcard session (like 192.168.1.*) */
	if ( nWildcardSession > L2TP_RULE_MAX_WILDCARD_SESSION_CNT)
	{
		LOG(LOG_ERR, "L2TP-Rule-Checker: More than one wildcard session!!! nWildcardSession %d", nWildcardSession);
		return FALSE;
	}

	/* Check Rule 2, When wildcard session is configures, no other session shall be configured. */
	if ( (nWildcardSession>0) && ( (nOtherSession>0) || (nDefaultSession>0) ) )
	{
		LOG(LOG_ERR, "L2TP-Rule-Checker: When wildcard session is configures, no other session shall be configured!!! nOtherSession %d nDefaultSession %d", nOtherSession, nDefaultSession);
		return FALSE;
	}

	/* Check Rule 3, Only one default session shall be configured (default session means vlanid is 0) */
	if ( nDefaultSession>L2TP_RULE_MAX_DEFAULT_SESSION_CNT )
	{
		LOG(LOG_ERR, "L2TP-Rule-Checker: Only one default session shall be configured!!! nDefaultSession %d", nDefaultSession);
		return FALSE;
	}

	LOG(LOG_INFO, "L2TP-Rule-Checker: valid session configuration, nWildcardSession %d nOtherSession %d nDefaultSession %d", nWildcardSession, nOtherSession, nDefaultSession);
    return TRUE;
} /* END of isValid_l2tp_conf() */



static void retrieve_vsd_content(struct dhcpMessage *packet, uint8_t *vendor_spec_info_ptr)
{
	uint8_t* mvs_option_ptr = NULL;

  /* L2TP specific variables */
  struct l2tp_config_rules l2tp_rules[MAX_L2TP_RULE_NUM];
  int i,k;
  int l2tp_config_received = 0;
  int l2tp_all_len = 0; // Do we need to use char???
  int l2tp_rule_len = 0;
  uint8_t *l2tp_rule_ptr;
  uint8_t opt_len;
  int bExceedMaxRuleSupport=FALSE;


  memset( l2tp_rules, 0, sizeof(l2tp_rules) );
  opt_len = *(vendor_spec_info_ptr - 1);

  /* check whether CPE_VENDOR_CLASS_ID exists, is vendor_spec_info_ptr
   * always longer than strlen(CPE_VENDOR_CLASS_ID)? */

  if ( memcmp( vendor_spec_info_ptr, MOT_WMX_VENDOR_CLASS, strlen(MOT_WMX_VENDOR_CLASS) ) == 0 )
  {
    if (memcmp(vendor_spec_info_ptr, CPE_VENDOR_CLASS_ID, strlen(CPE_VENDOR_CLASS_ID)) == 0)
    {
      /* vendor specific info for the CPE, which contains the following format
      *
      *   Vendor Class ID (CPE_VENDOR_CLASS_ID)
      *   MVS Option
      *   number of bytes
      *   option value
      *   MVS Option
      *   number of bytes
      *   option value
      *   MVS Option
      *   number of bytes
      *   option value
      *   MVS Option
      *   number of bytes
      *   option value
       *********************************************/

      /* point to the first mvs option */
       mvs_option_ptr = vendor_spec_info_ptr + strlen(CPE_VENDOR_CLASS_ID);
    }
  }
  else
  {
      /* CPE_VENDOR_CLASS_ID is not presented*/
      /* vendor specific info for the CPE, which contains the following format
       *
       *   MVS Option
       *   number of bytes
       *   option value
       *   MVS Option
       *   number of bytes
       *   option value
       *   MVS Option
       *   number of bytes
       *   option value
       *   MVS Option
       *   number of bytes
       *   option value
       *********************************************/
        mvs_option_ptr = vendor_spec_info_ptr;
   }

   if ( mvs_option_ptr != NULL )
   {
     while ( mvs_option_ptr < (vendor_spec_info_ptr + opt_len) )
     { /* As long as the mvs_option_ptr is in the range of the vendor opt len*/

      if (mvs_option_ptr[0] == L2TP_CONFIG)
      { /* find the L2TPv3 configuration options */

        /* Get L2TPv3 configuration length for all rules*/
        l2tp_all_len = mvs_option_ptr[1];
        if ( l2tp_all_len > (opt_len-2) )
            break;

        l2tp_rule_ptr = &mvs_option_ptr[2];
        /* Format of each rule is as follows:
           Type = 1 byte (0x02)
           Length of rule#1 = 1 byte
           Value
           Peer IP address = 4 bytes
           Session ID = 2 bytes
           Tunnel MTU = 2 bytes
           No of VLAN IDs = 1 byte
           VLAN ID#1 = 2 bytes
           VLAN ID#2 = 2 bytes
           VLAN ID#3 = 2 bytes
        */

        /* Tunnel rule option identifier */
        i=0;
        while ((l2tp_rule_ptr[0] == 0x02) && (l2tp_rule_ptr < mvs_option_ptr + l2tp_all_len + 2))
        {
            if( i >= MAX_L2TP_RULE_NUM )
            {
                LOG(LOG_INFO, "L2TP-Rule-Checker: wrong L2TP session configuration, exceed max rule support, should not over %d rules", MAX_L2TP_RULE_NUM);
                bExceedMaxRuleSupport=TRUE;
                break;
            }

          l2tp_rule_len = l2tp_rule_ptr[1];
          if (l2tp_rule_len >= 11)
          {
            sprintip(l2tp_rules[i].ipaddr, "", &l2tp_rule_ptr[2]); /* IP address*/
            l2tp_rules[i].ipaddr[17] = 0; /* Null terminating the IP address */
            LOG(LOG_INFO, "Peer IP address received: %s", l2tp_rules[i].ipaddr);
            memcpy(&l2tp_rules[i].session_id, &l2tp_rule_ptr[6], 4); /* Session ID*/
            l2tp_rules[i].session_id = ntohl(l2tp_rules[i].session_id);
            LOG(LOG_INFO, "Session ID received: %d", l2tp_rules[i].session_id);
            memcpy(&l2tp_rules[i].mtu_size, &l2tp_rule_ptr[10], 2); /* Tunnel MTU */
            l2tp_rules[i].mtu_size = ntohs(l2tp_rules[i].mtu_size);
#if 0
                     if(l2tp_rules[i].mtu_size < 576 || l2tp_rules[i].mtu_size > 1500)
                     {
                        LOG(LOG_ERR, "Invalid MTU size received in the DHCP ACK message");
                        break;
                     }

#endif
            LOG(LOG_INFO, "Session MTU size received: %d", l2tp_rules[i].mtu_size);
            l2tp_rules[i].vlan_num = l2tp_rule_ptr[12];
            LOG(LOG_INFO, "Number of VLAN IDs received: %d", l2tp_rules[i].vlan_num);

#if 0
                     if(l2tp_rules[i].vlan_num > 3)
                     {
                        LOG(LOG_ERR, "Currenlt Max 3 Vlan Ids supported in per session");
                        break;
                     }
#endif

            for (k=0; k < l2tp_rules[i].vlan_num && k < 3 ; k++)
            {
               memcpy(&l2tp_rules[i].vlan_id[k],  &l2tp_rule_ptr[12+1+k*2], 2); /* VLAN IDs upto 3*/
               l2tp_rules[i].vlan_id[k] = ntohs(l2tp_rules[i].vlan_id[k]);
            }

            /* IP Address,Session-ID,MTU,vlan_num = 11 */
            if(l2tp_rule_len == (11 + (2*l2tp_rules[i].vlan_num)))
            {
               l2tp_rules[i].cookie_len =0;
            }
            else
            {
               l2tp_rules[i].cookie_len = l2tp_rule_ptr[13+(2*l2tp_rules[i].vlan_num)];
               if((l2tp_rules[i].cookie_len !=0) && (l2tp_rules[i].cookie_len <= 8 ))
               {
                 int n;
                 unsigned char temp_str[10];

                 l2tp_rules[i].cookie[0]='\0';
                 for(n=0; n<l2tp_rules[i].cookie_len; n++)
                 {
                    sprintf(temp_str,"%02x",l2tp_rule_ptr[14+(2*l2tp_rules[i].vlan_num)+n]);
                    strcat(l2tp_rules[i].cookie,temp_str);
                  }
                }
                else
                {
                  l2tp_rules[i].cookie_len = 0; /* Make the cookie length to zero to ignore it */
                  LOG(LOG_ERR, "Cookie len =%u. So,Ignoring the cookie. Cookie len should be 1-8",l2tp_rules[i].cookie_len);
                }

              }
              LOG(LOG_INFO,"\n cookie=%s cooke_len=%u",l2tp_rules[i].cookie,l2tp_rules[i].cookie_len);

              l2tp_rule_ptr += (l2tp_rule_len+2);
              i++;

              /* Indicate L2TP configuration is successfully received */
              l2tp_config_received++;
          }// end if(l2tp_rule_len >= 11)
          else
          {
             LOG(LOG_ERR, "Insufficient L2TP rule length passed in DHCP ACK message");
             break;
          }

         } /* end of l2tp related while loop */

         if(bExceedMaxRuleSupport)
             l2tp_config_received=0;

         if(!l2tp_config_received)
         {
            LOG(LOG_ERR, "No/Invalid L2TP rules present in DHCP ACK message");

         }
         else
         {
            LOG(LOG_INFO, "Number of rules after parsing: %d", l2tp_config_received);

            if ( TRUE != isValid_l2tp_conf(l2tp_rules, l2tp_config_received))
            {
                LOG(LOG_ERR, "Invalid L2TP rules present in DHCP ACK message, discard L2TP configuration!!!");
            }
            else
            {
                LOG(LOG_INFO, "L2TP rule information sent to core module & written to %s%s", TEMP_CPE_STATS_PATH, L2TP_CONFIG_FILE);
            }
         }

      } //end if(mvs_option_ptr[0] == L2TP_CONFIG) /* end of finding the L2TPv3 configuration options */


      /* points to the next mvs option */
      mvs_option_ptr = mvs_option_ptr + 2 + mvs_option_ptr[1];

     } /* end of while loop */
    }/* end if(mvs_option_ptr != NULL)*/

}

/* put all the parameters into an environment */
static char **fill_envp(struct dhcpMessage *packet)
{
	int num_options = 0;
	int i, j;
	char **envp;
	uint8_t *temp;
	struct in_addr subnet;
	char over = 0;
	uint8_t *vendor_spec_info_ptr = NULL;



	if (packet == NULL)
		num_options = 0;
	else {
		for (i = 0; dhcp_options[i].code; i++)
			if (get_option(packet, dhcp_options[i].code)) {
				num_options++;
				if (dhcp_options[i].code == DHCP_SUBNET)
					num_options++; /* for mton */
			}
		if (packet->siaddr) num_options++;
		if ((temp = get_option(packet, DHCP_OPTION_OVER)))
			over = *temp;
		if (!(over & FILE_FIELD) && packet->file[0]) num_options++;
		if (!(over & SNAME_FIELD) && packet->sname[0]) num_options++;
	}

	envp = xcalloc(sizeof(char *), num_options + 5);
	j = 0;
	asprintf(&envp[j++], "interface=%s", client_config.interface);
	asprintf(&envp[j++], "%s=%s", "PATH",
		getenv("PATH") ? : "/bin:/usr/bin:/sbin:/usr/sbin");
	asprintf(&envp[j++], "%s=%s", "HOME", getenv("HOME") ? : "/");

	if (packet == NULL) return envp;

	envp[j] = xmalloc(sizeof("ip=255.255.255.255"));
	sprintip(envp[j++], "ip=", (uint8_t *) &packet->yiaddr);


  /* Retrieve L2TPv3 rules from vendor specfific option*/
  vendor_spec_info_ptr = get_option(packet, DHCP_VENDOR_SPEC_INFO);
  if(vendor_spec_info_ptr != NULL)
  	retrieve_vsd_content(packet, vendor_spec_info_ptr);
  else
    LOG(LOG_INFO, "Unable to obtain vendor specific option from dhcp ack.");




	for (i = 0; dhcp_options[i].code; i++) {
		if (!(temp = get_option(packet, dhcp_options[i].code)))
			continue;
		envp[j] = xmalloc(upper_length(temp[OPT_LEN - 2],
			dhcp_options[i].flags & TYPE_MASK) + strlen(dhcp_options[i].name) + 2);
		fill_options(envp[j++], temp, &dhcp_options[i]);

		/* Fill in a subnet bits option for things like /24 */
		if (dhcp_options[i].code == DHCP_SUBNET) {
			memcpy(&subnet, temp, 4);
			asprintf(&envp[j++], "mask=%d", mton(&subnet));
		}
	}
	if (packet->siaddr) {
		envp[j] = xmalloc(sizeof("siaddr=255.255.255.255"));
		sprintip(envp[j++], "siaddr=", (uint8_t *) &packet->siaddr);
	}
	if (!(over & FILE_FIELD) && packet->file[0]) {
		/* watch out for invalid packets */
		packet->file[sizeof(packet->file) - 1] = '\0';
		asprintf(&envp[j++], "boot_file=%s", packet->file);
	}
	if (!(over & SNAME_FIELD) && packet->sname[0]) {
		/* watch out for invalid packets */
		packet->sname[sizeof(packet->sname) - 1] = '\0';
		asprintf(&envp[j++], "sname=%s", packet->sname);
	}
	return envp;
}

#define WAN_IPVAR_TRIGGER     "/tmp/var/cpe/trigger/share/global/wan_ip_change"

/* Call a script with a par file and env vars */
void run_script(struct dhcpMessage *packet, const char *name)
{
	int pid;
	char **envp, **curr;
  int i=0;

	if (client_config.script == NULL)
		return;

	DEBUG(LOG_INFO, "vforking and execle'ing %s", client_config.script);

	envp = fill_envp(packet);
	while(envp[i])
	{
		DEBUG(LOG_INFO, "@@@ %d options ==>%s",i,envp[i]);
		i++;
	}

	/* call script */
	pid = vfork();
	if (pid) {
		waitpid(pid, NULL, 0);
		for (curr = envp; *curr; curr++) free(*curr);
		free(envp);
		return;
	} else if (pid == 0) {
		/* close fd's? */

		/* exec script */
		execle(client_config.script, client_config.script,
		       name, NULL, envp);
		LOG(LOG_ERR, "script %s failed: %m", client_config.script);
		exit(1);
	}
}
