/*
    Copyright (C) 2002-2005  Thomas Ries <tries@gmx.net>

    This file is part of Siproxd.
    
    Siproxd 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.
    
    Siproxd 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 Siproxd; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
*/

#include "config.h"

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <netinet/in.h>

#include <osipparser2/osip_parser.h>

#include "siproxd.h"
#include "log.h"

static char const ident[]="$Id: readconf.c,v 1.30 2005/01/08 10:05:12 hb9xar Exp $";

/* configuration storage */
extern struct siproxd_config configuration;

/* prototypes used locally only */
static int parse_config (FILE *configfile);


/* try to open (whichever is found first):
 *	<name>
 *	$HOME/.<name>rc
 *	/etc/<name>.conf
 *	/usr/etc/<name>.conf
 *	/usr/local/etc/<name>.conf
 *
 * RETURNS
 *	STS_SUCCESS on success
 *	STS_FAILURE on error
 */
int read_config(char *name, int search) {
   int sts;
   FILE *configfile=NULL;
   int i;
   char tmp[256];
   const char *completion[] = {
	"%s/.%src",		/* this one is special... (idx=0)*/
	SIPROXDCONFPATH "/%s.conf",
	"/etc/%s.conf",
	"/usr/etc/%s.conf",
	"/usr/local/etc/%s.conf",
	"/var/siproxd/siproxd.conf",
	NULL };


   DEBUGC(DBCLASS_CONFIG,"trying to read config file");

   /* shall I search the config file myself ? */
   if (search != 0) {
      /* yup, try to find it */
      for (i=0; completion[i]!=NULL; i++) {
	 switch (i) {
	 case 0:
            sprintf(tmp,completion[i],getenv("HOME"),name);
	    break;
	 default:
            sprintf(tmp,completion[i],name);
	    break;
	 }
	 DEBUGC(DBCLASS_CONFIG,"... trying %s",tmp);
         configfile = fopen(tmp,"r");
	 if (configfile==NULL) continue;
	 break; /* got config file */
      }
   } else {
         /* don't search it, just try the one given file */
	 DEBUGC(DBCLASS_CONFIG,"... trying %s",name);
         configfile = fopen(name,"r");
   }

   /* config file not found or unable to open for read */
   if (configfile==NULL) {
      ERROR ("could not open config file: %s", strerror(errno));
      return STS_FAILURE;
   }

   sts = parse_config(configfile);
   fclose(configfile);

   /*
    * Post-process configuration variables that have conditions that
    * must be met; warn if we have to adjust any.
    */
   if (configuration.rtp_port_low & 0x01) {
      /* rtp_port_low must be an even number... */
      configuration.rtp_port_low = (configuration.rtp_port_low + 1) & ~0x01;
      WARN("rtp_port_low should be an even number; it's been rounded up to %i",
	   configuration.rtp_port_low);
   }
   if (configuration.rtp_port_high & 0x01) {
      /* rtp_high_port should be either the top RTP port allowed, */
      /* or the top RTCP port allowed.  If the latter, then reset */
      /* to the former... Don't need a warning here.  It's okay.  */
      configuration.rtp_port_high = configuration.rtp_port_high & ~0x01;
      DEBUGC(DBCLASS_CONFIG, "rounded rtp_port_high down to %i",
	     configuration.rtp_port_high);
   }

   return sts;
}


/*
 * parse configuration file
 *
 * RETURNS
 *	STS_SUCCESS on success
 *	STS_FAILURE on error
 */
static int parse_config (FILE *configfile) {
   char buff[1024];
   char *ptr;
   int i;
   int k;
   int num;
   char *tmpptr;

   struct cfgopts {
      char *keyword;
      enum type {TYP_INT4, TYP_STRING, TYP_FLOAT, TYP_STRINGA} type;
      void *dest;
   } configoptions[] = {
      { "debug_level",         TYP_INT4,   &configuration.debuglevel },
      { "debug_port",          TYP_INT4,   &configuration.debugport },
      { "sip_listen_port",     TYP_INT4,   &configuration.sip_listen_port },
      { "daemonize",           TYP_INT4,   &configuration.daemonize },
      { "silence_log",         TYP_INT4,   &configuration.silence_log },
      { "if_inbound",          TYP_STRING, &configuration.inbound_if },
      /* mod by weigy on 20100531 for the multi wan route select */
      #ifndef MULTI_WAN
      { "if_outbound",         TYP_STRING, &configuration.outbound_if },
      #endif
      /* mod end */
      { "rtp_port_low",        TYP_INT4,   &configuration.rtp_port_low },
      { "rtp_port_high",       TYP_INT4,   &configuration.rtp_port_high },
      { "rtp_timeout",         TYP_INT4,   &configuration.rtp_timeout },
      { "rtp_proxy_enable",    TYP_INT4,   &configuration.rtp_proxy_enable },
      { "user",                TYP_STRING, &configuration.user },
      { "chrootjail",          TYP_STRING, &configuration.chrootjail },
      { "hosts_allow_reg",     TYP_STRING, &configuration.hosts_allow_reg },
      { "hosts_allow_sip",     TYP_STRING, &configuration.hosts_allow_sip },
      { "hosts_deny_sip",      TYP_STRING, &configuration.hosts_deny_sip },
      { "hosts_deny_sip",      TYP_STRING, &configuration.hosts_deny_sip },
      { "proxy_auth_realm",    TYP_STRING, &configuration.proxy_auth_realm },
      { "proxy_auth_passwd",   TYP_STRING, &configuration.proxy_auth_passwd },
      { "proxy_auth_pwfile",   TYP_STRING, &configuration.proxy_auth_pwfile },
      { "mask_host",           TYP_STRINGA,&configuration.mask_host },
      { "masked_host",         TYP_STRINGA,&configuration.masked_host },
      { "outbound_proxy_host", TYP_STRING, &configuration.outbound_proxy_host },
      { "outbound_proxy_port", TYP_INT4,   &configuration.outbound_proxy_port },
      { "outbound_domain_name",TYP_STRINGA,&configuration.outbound_proxy_domain_name },
      { "outbound_domain_host",TYP_STRINGA,&configuration.outbound_proxy_domain_host },
      { "outbound_domain_port",TYP_STRINGA,&configuration.outbound_proxy_domain_port },
      { "registration_file",   TYP_STRING ,&configuration.registrationfile },
      { "log_calls",           TYP_INT4,   &configuration.log_calls },
      { "pid_file",            TYP_STRING ,&configuration.pid_file },
      { "default_expires",     TYP_INT4   ,&configuration.default_expires },
/*add by xuezhong for interface choose on 20091009*/      
      { "total_outboundinterface", TYP_STRING   ,&configuration.totalinterface},
/*end add*/      
      /* add by weigy on 20100531 for the multi wan route select */
      #if MULTI_WAN
      { "wan1_name",            TYP_STRING,  &configuration.wan_name[0]},
      { "wan1_type",            TYP_INT4,    &configuration.wan_type[0]},
      { "wan1_lan",             TYP_INT4,    &configuration.wan_lan[0]},
      { "wan2_name",            TYP_STRING,  &configuration.wan_name[1]},
      { "wan2_type",            TYP_INT4,    &configuration.wan_type[1]},
      { "wan2_lan",             TYP_INT4,    &configuration.wan_lan[1]},
      { "wan3_name",            TYP_STRING,  &configuration.wan_name[2]},
      { "wan3_type",            TYP_INT4,    &configuration.wan_type[2]},
      { "wan3_lan",             TYP_INT4,    &configuration.wan_lan[2]},
      { "wan4_name",            TYP_STRING,  &configuration.wan_name[3]},
      { "wan4_type",            TYP_INT4,    &configuration.wan_type[3]},
      { "wan4_lan",             TYP_INT4,    &configuration.wan_lan[3]},
      { "wan5_name",            TYP_STRING,  &configuration.wan_name[4]},
      { "wan5_type",            TYP_INT4,    &configuration.wan_type[4]},
      { "wan5_lan",             TYP_INT4,    &configuration.wan_lan[4]},
      { "wan6_name",            TYP_STRING,  &configuration.wan_name[5]},
      { "wan6_type",            TYP_INT4,    &configuration.wan_type[5]},
      { "wan6_lan",             TYP_INT4,    &configuration.wan_lan[5]},
      { "wan7_name",            TYP_STRING,  &configuration.wan_name[6]},
      { "wan7_type",            TYP_INT4,    &configuration.wan_type[6]},
      { "wan7_lan",             TYP_INT4,    &configuration.wan_lan[6]},
      { "wan8_name",            TYP_STRING,  &configuration.wan_name[7]},
      { "wan8_type",            TYP_INT4,    &configuration.wan_type[7]},
      { "wan8_lan",             TYP_INT4,    &configuration.wan_lan[7]},
      #endif
      /* add end */
      {0, 0, 0}
   };


   while (fgets(buff,sizeof(buff),configfile) != NULL) {
      /* life insurance */
      buff[sizeof(buff)-1]='\0';

      /* strip New line & CR if present */
      for (i=1; i<=2; i++) {
         if ((buff[strlen(buff)-i]=='\n') || (buff[strlen(buff)-i]=='\r')) {
            buff[strlen(buff)-i]='\0';
         }
      }

      /* strip emtpy lines */
      if (strlen(buff) == 0) continue;

      /* strip comments and line with only whitespaces */
      for (i=0;i<strlen(buff);i++) {
         if ((buff[i] == ' ') || (buff[i] == '\t')) continue;
         if (buff[i] =='#') i=strlen(buff);
         break;
      }
      if (i == strlen(buff)) continue;

      DEBUGC(DBCLASS_CONFIG,"pc:\"%s\"",buff);

      /* scan for known keyword */
      for (k=0; configoptions[k].keyword != NULL; k++) {
         if ((ptr=strstr(buff, configoptions[k].keyword)) != NULL) {
            ptr += strlen(configoptions[k].keyword);
            DEBUGC(DBCLASS_CONFIG,"got keyword:\"%s\"",
	                          configoptions[k].keyword);

	    /* check for argument separated by '=' */
            if ((ptr=strchr(ptr,'=')) == NULL) {;
	       ERROR("argument missing to config parameter %s",
	             configoptions[k].keyword);
	       break;
            }
	    do {ptr++;} while (*ptr == ' '); /* skip spaces after '=' */
            
            DEBUGC(DBCLASS_CONFIG,"got argument:\"%s\"",ptr);

	    num=0;
	    if (strlen(ptr) <= 0) {
	       WARN("empty argument in config file, line:\"%s\"",buff);
	       break;
            }

            switch (configoptions[k].type) {

	    //
            // Integer4
            //
	    case TYP_INT4:
	         num=sscanf(ptr,"%i",(int*)configoptions[k].dest);
                 DEBUGC(DBCLASS_BABBLE,"INT4=%i",*(int*)configoptions[k].dest);
	      break;	    

	    //
	    // String
	    //
	    case TYP_STRING:
	         /* the %as within sscanf seems to be not too portable.
                  * it is supposed to allocate the memory
                  * num=sscanf(ptr,"%as",(char**)configoptions[k].dest);
                  */

		 /* figure out the amount of space we need */
	         num=strlen(ptr)+1; /* include terminating zero!*/
                 tmpptr=(char*)malloc(num);
                 memcpy(configoptions[k].dest, &tmpptr, sizeof(tmpptr));
	         num=sscanf(ptr,"%s",tmpptr);
                 DEBUGC(DBCLASS_BABBLE,"STRING=%s",
                         *(char**)configoptions[k].dest);
	      break;	    

	    //
	    // String array
	    //
	    case TYP_STRINGA:
            {
		 /* figure out the amount of space we need */
                 char **dst;
                 int used=((stringa_t*)(configoptions[k].dest))->used;
		 // do I hace space left?
                 if (used<=CFG_STRARR_SIZE){
	            num=strlen(ptr)+1; /* include terminating zero!*/
                    tmpptr=(char*)malloc(num);
		    dst=&((stringa_t*)(configoptions[k].dest))->
                         string[used];
                    memcpy(dst, &tmpptr, sizeof(tmpptr));
	            num=sscanf(ptr,"%s",tmpptr);
		    DEBUGC(DBCLASS_BABBLE,"STRINGA[%i]=%s", used, (char*) (
			   ((stringa_t*)(configoptions[k].dest))->string[used]) );
		    ((stringa_t*)(configoptions[k].dest))->used++;
                 } else {
		    ERROR("no more space left in config string array %s",
                          configoptions[k].keyword);
                 }
	      break;
            }

	    default:
	      break;
	    }
	    if (num == 0) {
	       ERROR("illegal format in config file, line:\"%s\"",buff);
	    }

            break;
	 }
      } // for configoptions

      /*
       * complain if we hit a unknown keyword
       */
       if (configoptions[k].keyword == NULL) {
	  ERROR("unknown keyword in config file, line:\"%s\"",buff);
       }
   } // while
   return STS_SUCCESS;
}


int make_default_config(void){
   memset (&configuration, 0, sizeof(configuration));
   configuration.sip_listen_port=SIP_PORT;
   configuration.default_expires=DEFAULT_EXPIRES;

   return STS_SUCCESS;
}
