/*
Copyright (C) 2003-2004 Narcis Ilisei

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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

/*
	Dyn Dns update main implementation file 
	Author: narcis Ilisei
	Date: May 2003

	History:
		- first implemetnation
		- 18 May 2003 : cmd line option reading added - 
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "dyndns.h"
#include "debug_if.h"
#include "base64.h"
#include "get_cmd.h"

/* command line options */

static RC_TYPE help_handler(CMD_DATA *p_cmd, int current_nr, void *p_context);
static RC_TYPE get_username_handler(CMD_DATA *p_cmd, int current_nr, void *p_context);
static RC_TYPE get_password_handler(CMD_DATA *p_cmd, int current_nr, void *p_context);
static RC_TYPE get_alias_handler(CMD_DATA *p_cmd, int current_nr, void *p_context);
static RC_TYPE get_dns_server_name_handler(CMD_DATA *p_cmd, int current_nr, void *p_context);
static RC_TYPE get_ip_server_name_handler(CMD_DATA *p_cmd, int current_nr, void *p_context);
static RC_TYPE get_dyndns_system_handler(CMD_DATA *p_cmd, int current_nr, void *p_context);
static RC_TYPE get_update_period_handler(CMD_DATA *p_cmd, int current_nr, void *p_context);
static RC_TYPE get_update_period_sec_handler(CMD_DATA *p_cmd, int current_nr, void *p_context);
static RC_TYPE get_forced_update_period_handler(CMD_DATA *p_cmd, int current_nr, void *p_context);
static RC_TYPE get_logfile_name(CMD_DATA *p_cmd, int current_nr, void *p_context);
static RC_TYPE set_silent_handler(CMD_DATA *p_cmd, int current_nr, void *p_context);
static RC_TYPE set_verbose_handler(CMD_DATA *p_cmd, int current_nr, void *p_context);
static RC_TYPE get_proxy_server_handler(CMD_DATA *p_cmd, int current_nr, void *p_context);
static RC_TYPE get_options_from_file_handler(CMD_DATA *p_cmd, int current_nr, void *p_context);
static RC_TYPE set_iterations_handler(CMD_DATA *p_cmd, int current_nr, void *p_context);

static CMD_DESCRIPTION_TYPE cmd_options_table[] = 
{
	{"--help",		0,	{help_handler, NULL},	"help" },
	{"-h",			0,	{help_handler, NULL},	"help" },

	{"--username",	1,	{get_username_handler, NULL},	"your  membername/ hash"},
	{"-u",			1,	{get_username_handler, NULL},	"your  membername / hash"},

	{"--password",	1,	{get_password_handler, NULL},	"your password. Optional."},
	{"-p",			1,	{get_password_handler, NULL},	"your password"},

	{"--alias",		1,	{get_alias_handler, NULL},	"alias host name. this option can appear multiple times." },
	{"-a",			1,	{get_alias_handler, NULL},	"alias host name. this option can appear multiple times." },

	{"--input_file", 1, {get_options_from_file_handler, NULL}, "the file containing [further] inadyn options"},
	
	{"--ip_server_name",	1,	{get_ip_server_name_handler, NULL},
        "[NAME] - local IP is detected calling a CGI from this server name. default checkip.dyndns.org"},

	{"--dyndns_server_name", 1,	{get_dns_server_name_handler, NULL},	
            "[<NAME>[:port]] \n"
            "\t\tThe server that receives the update DNS request.  \n"
            "\t\tFor dyndns.org is: members.dyndns.org. \n"
            "\t\tFor freedns.afraid.org is: freedns.afraid.org \n"
            "\t\tIf no proxy is wanted, then it is enough to set the dyndns system. The default servers will be taken."},

	{"--dyndns_system",	1,	{get_dyndns_system_handler, NULL},	
            "[NAME] - optional DYNDNS service type. SHOULD be one of the following: \n"
            "\t\t-For dyndns.org DNS system: dyndns@dyndns.org OR statdns@dyndns.org OR custom@dyndns.org.\n"
            "\t\t-For freedns.afraid.org DNS system: default@freedns.afraid.org\n"
            "\t\tDEFAULT value is intended for default service at dyndns.org (most users): dyndns@dyndns.org"},

    {"--proxy_server", 1, {get_proxy_server_handler, NULL},
            "[NAME[:port]]  - the http proxy server name and port. Default is none."},
	{"--update_period",	1,	{get_update_period_handler, NULL},	
            "how often the IP is checked. The period is in [ms]. Default is about 1 min. Max is 10 days"},
	{"--update_period_sec",	1,	{get_update_period_sec_handler, NULL},	"how often the IP is checked. The period is in [sec]. Default is about 1 min. Max is 10 days"},
	{"--forced_update_period", 1,   {get_forced_update_period_handler, NULL},"how often the IP is updated even if it is not changed. [in sec]"},

	{"--log_file",	1,	{get_logfile_name, NULL},		"log file path abd name"},
	{"--background", 0,	{set_silent_handler, NULL},		"run in background. output to log file (works on windows only)"},

	{"--verbose",	1,	{set_verbose_handler, NULL},	"set dbg level. 0 to 5"},

	{"--iterations",	1,	{set_iterations_handler, NULL},	"set the number of DNS updates. Default is 0, which means infinity."},
	{NULL,		0,	{0, NULL},	NULL }
};


static void print_help_page(void)
{
	DBG_PRINTF(("\n\n\n"
	"			INADYN Help\n\n"
	"	INADYN is a dynamic DNS client. That is, it maintains the IP address\n"
	"of a host name. It periodically checks whether the IP address of the current machine\n"
	"(the external visible IP address of the machine that runs INADYN) has changed.\n"
    "If yes it performs an update in the dynamic dns server.\n\n"
    "Typical usage: \n"
    "\t-for dyndns.org system: \n"
    "\t\tinadyn -u username -p password -a my.registrated.name \n"
    "\t-for freedns.afraid.org:\n"
    "\t\t inadyn --dyndns_system default@freedns.afraid.org -a my.registrated.name,hash -a anothername,hash2\n"
    "\t\t\t 'hash' is extracted from the grab url batch file that is downloaded from freedns.afraid.org\n\n"
	"Parameters:\n"));

	{
		CMD_DESCRIPTION_TYPE *it = cmd_options_table;
		
		while( it->p_option != NULL)
		{
			DBG_PRINTF((				
				"\t'%s': %s\n",
				it->p_option, it->p_description));
			++it;
		}
	}
	DBG_PRINTF(("\n\n\n"));
}

static RC_TYPE help_handler(CMD_DATA *p_cmd, int current_nr, void *p_context)
{
	print_help_page();
	return RC_ERROR;
}

static RC_TYPE set_verbose_handler(CMD_DATA *p_cmd, int current_nr, void *p_context)
{
	DYN_DNS_CLIENT *p_self = (DYN_DNS_CLIENT *) p_context;
	if (p_self == NULL)
	{
		return RC_INVALID_POINTER;
	}

	if (sscanf(p_cmd->argv[current_nr], "%d", &p_self->dbg.level) != 1)
	{
		return RC_DYNDNS_INVALID_OPTION;
	}
	return RC_OK;
}

static RC_TYPE set_iterations_handler(CMD_DATA *p_cmd, int current_nr, void *p_context)
{
	DYN_DNS_CLIENT *p_self = (DYN_DNS_CLIENT *) p_context;
	if (p_self == NULL)
	{
		return RC_INVALID_POINTER;
	}

	if (sscanf(p_cmd->argv[current_nr], "%d", &p_self->total_iterations) != 1)
	{
		return RC_DYNDNS_INVALID_OPTION;
	}
	
	p_self->total_iterations = (p_self->sleep_sec < 0) ?  DYNDNS_DEFAULT_ITERATIONS : p_self->total_iterations;
	return RC_OK;
}

static RC_TYPE set_silent_handler(CMD_DATA *p_cmd, int current_nr, void *p_context)
{
	DYN_DNS_CLIENT *p_self = (DYN_DNS_CLIENT *) p_context;
	if (p_self == NULL)
	{
		return RC_INVALID_POINTER;
	}

	p_self->run_in_background = TRUE;
	return RC_OK;
}


static RC_TYPE get_logfile_name(CMD_DATA *p_cmd, int current_nr, void *p_context)
{
	DYN_DNS_CLIENT *p_self = (DYN_DNS_CLIENT *) p_context;
	if (p_self == NULL)
	{
		return RC_INVALID_POINTER;
	}

	if (sizeof(p_self->dbg.p_logfilename) < strlen(p_cmd->argv[current_nr]))
	{
		return  RC_DYNDNS_BUFFER_TOO_SMALL;
	}
	strcpy(p_self->dbg.p_logfilename, p_cmd->argv[current_nr]);
	return RC_OK;
}


static RC_TYPE get_username_handler(CMD_DATA *p_cmd, int current_nr, void *p_context)
{
	DYN_DNS_CLIENT *p_self = (DYN_DNS_CLIENT *) p_context;
	if (p_self == NULL)
	{
		return RC_INVALID_POINTER;
	}

	/*user*/
	if (sizeof(p_self->info.credentials.my_username) < strlen(p_cmd->argv[current_nr]))
	{
		return  RC_DYNDNS_BUFFER_TOO_SMALL;
	}
	strcpy(p_self->info.credentials.my_username, p_cmd->argv[current_nr]);

	return RC_OK;
}

static RC_TYPE get_password_handler(CMD_DATA *p_cmd, int current_nr, void *p_context)
{
	DYN_DNS_CLIENT *p_self = (DYN_DNS_CLIENT *) p_context;
	if (p_self == NULL)
	{
		return RC_INVALID_POINTER;
	}

	/*user*/
	if (sizeof(p_self->info.credentials.my_password) < strlen(p_cmd->argv[current_nr]))
	{
		return  RC_DYNDNS_BUFFER_TOO_SMALL;
	}

	strcpy(p_self->info.credentials.my_password, (p_cmd->argv[current_nr]));
	return RC_OK;
}

/**
    Parses alias,hash.
    Example: blabla.domain.com,hashahashshahah
    Action:
	-search by ',' and replace the ',' with 0
	-read hash and alias
*/
static RC_TYPE get_alias_handler(CMD_DATA *p_cmd, int current_nr, void *p_context)
{
	DYN_DNS_CLIENT *p_self = (DYN_DNS_CLIENT *) p_context;
        char *p_hash = NULL;

	if (p_self == NULL)
	{
		return RC_INVALID_POINTER;
	}

	if (p_self->alias_info.count >= DYNDNS_MAX_ALIAS_NUMBER)
	{
		return RC_DYNDNS_TOO_MANY_ALIASES;
	}

	/*hash*/
        p_hash = strstr(p_cmd->argv[current_nr],",");
        if (p_hash)
	{
	    if (sizeof(*p_self->alias_info.hashes) < strlen(p_hash))
	    {
		return RC_DYNDNS_BUFFER_TOO_SMALL;
	    }
	    strcpy(p_self->alias_info.hashes[p_self->alias_info.count].str, p_hash);
	    *p_hash = '\0';
	}


	/*user*/
	if (sizeof(p_self->alias_info.names[p_self->alias_info.count]) < strlen(p_cmd->argv[current_nr]))
	{
		return  RC_DYNDNS_BUFFER_TOO_SMALL;
	}
	strcpy(p_self->alias_info.names[p_self->alias_info.count].name, (p_cmd->argv[current_nr]));

	p_self->alias_info.count ++;
	return RC_OK;
}

static RC_TYPE get_name_and_port(char *p_src, char *p_dest_name, int *p_dest_port)
{
    const char *p_port = NULL;
    p_port = strstr(p_src,":");
    if (p_port)
    {
        int port_nr, len;
        int port_ok = sscanf(p_port + 1, "%d",&port_nr);
        if (port_ok != 1)
        {
            return RC_DYNDNS_INVALID_OPTION;
        }
        *p_dest_port = port_nr;
        len = p_port - p_src;
        memcpy(p_dest_name, p_src, len);
        p_dest_name[len] = 0;
    }
    else
    {
        strcpy(p_dest_name, p_src);
    }
    return RC_OK;
}

/** Returns the svr name and port if the format is :
 * name:port.
 */
static RC_TYPE get_ip_server_name_handler(CMD_DATA *p_cmd, int current_nr, void *p_context)
{
	DYN_DNS_CLIENT *p_self = (DYN_DNS_CLIENT *) p_context;
    RC_TYPE rc;
    int port = -1;

	if (p_self == NULL)
	{
		return RC_INVALID_POINTER;
	}

	/*user*/
	if (sizeof(p_self->info.ip_server_name) < strlen(p_cmd->argv[current_nr]))
	{
		return  RC_DYNDNS_BUFFER_TOO_SMALL;
	}

    p_self->info.ip_server_name.port = HTTP_DEFAULT_PORT;
    rc = get_name_and_port(p_cmd->argv[current_nr], p_self->info.ip_server_name.name, &port);
    if (rc == RC_OK && port != -1)
    {
        p_self->info.ip_server_name.port = port;
    }                                   
	return rc;
}

static RC_TYPE get_dns_server_name_handler(CMD_DATA *p_cmd, int current_nr, void *p_context)
{
	DYN_DNS_CLIENT *p_self = (DYN_DNS_CLIENT *) p_context;
    RC_TYPE rc;
    int port = -1;

	if (p_self == NULL)
	{
		return RC_INVALID_POINTER;
	}

	/*user*/
	if (sizeof(p_self->info.dyndns_server_name) < strlen(p_cmd->argv[current_nr]))
	{
		return  RC_DYNDNS_BUFFER_TOO_SMALL;
	}
    
    p_self->info.dyndns_server_name.port = HTTP_DEFAULT_PORT;
    rc = get_name_and_port(p_cmd->argv[current_nr], p_self->info.dyndns_server_name.name, &port);
    if (rc == RC_OK && port != -1)
    {
        p_self->info.dyndns_server_name.port = port;
    }                                   
	return rc;
}

/* returns the proxy server nme and port
*/
static RC_TYPE get_proxy_server_handler(CMD_DATA *p_cmd, int current_nr, void *p_context)
{
	DYN_DNS_CLIENT *p_self = (DYN_DNS_CLIENT *) p_context;
    RC_TYPE rc;
    int port = -1;

	if (p_self == NULL)
	{
		return RC_INVALID_POINTER;
	}

	/*user*/
	if (sizeof(p_self->info.proxy_server_name) < strlen(p_cmd->argv[current_nr]))
	{
		return  RC_DYNDNS_BUFFER_TOO_SMALL;
	}
    
    p_self->info.proxy_server_name.port = HTTP_DEFAULT_PORT;
    rc = get_name_and_port(p_cmd->argv[current_nr], p_self->info.proxy_server_name.name, &port);
    if (rc == RC_OK && port != -1)
    {
        p_self->info.proxy_server_name.port = port;
    }                                   
	return rc;    
}
/* Read the dyndnds name update period.
   and impose the max and min limits
*/

static RC_TYPE get_update_period_handler(CMD_DATA *p_cmd, int current_nr, void *p_context)
{
	DYN_DNS_CLIENT *p_self = (DYN_DNS_CLIENT *) p_context;
	if (p_self == NULL)
	{
		return RC_INVALID_POINTER;
	}

	if (sscanf(p_cmd->argv[current_nr], "%d", &p_self->sleep_sec) != 1)
	{
		return RC_DYNDNS_INVALID_OPTION;
	}
	
	p_self->sleep_sec /= 1000;
	p_self->sleep_sec = (p_self->sleep_sec < DYNDNS_MIN_SLEEP) ?  DYNDNS_MIN_SLEEP: p_self->sleep_sec;
	(p_self->sleep_sec > DYNDNS_MAX_SLEEP) ?  p_self->sleep_sec = DYNDNS_MAX_SLEEP: 1;	

	return RC_OK;
}

static RC_TYPE get_update_period_sec_handler(CMD_DATA *p_cmd, int current_nr, void *p_context)
{
	DYN_DNS_CLIENT *p_self = (DYN_DNS_CLIENT *) p_context;
	if (p_self == NULL)
	{
		return RC_INVALID_POINTER;
	}

	if (sscanf(p_cmd->argv[current_nr], "%d", &p_self->sleep_sec) != 1)
	{
		return RC_DYNDNS_INVALID_OPTION;
	}
	
	p_self->sleep_sec = (p_self->sleep_sec < DYNDNS_MIN_SLEEP) ?  DYNDNS_MIN_SLEEP: p_self->sleep_sec;
	(p_self->sleep_sec > DYNDNS_MAX_SLEEP) ?  p_self->sleep_sec = DYNDNS_MAX_SLEEP: 1;	

	return RC_OK;
}

static RC_TYPE get_forced_update_period_handler(CMD_DATA *p_cmd, int current_nr, void *p_context)
{
	DYN_DNS_CLIENT *p_self = (DYN_DNS_CLIENT *) p_context;
	if (p_self == NULL)
	{
		return RC_INVALID_POINTER;
	}

	if (sscanf(p_cmd->argv[current_nr], "%d", &p_self->forced_update_period_sec) != 1)
	{
		return RC_DYNDNS_INVALID_OPTION;
	}
	
	return RC_OK;
}

/** 
    Searches the DYNDNS system by the argument.
    Input is like: system@server.name
    system=statdns|custom|dyndns|default
    server name = dyndns.org | freedns.afraid.org
    The result is a pointer in the table of DNS systems.
*/
static RC_TYPE get_dyndns_system_handler(CMD_DATA *p_cmd, int current_nr, void *p_context)
{
    DYNDNS_SYSTEM *p_dns_system = NULL;
	DYN_DNS_CLIENT *p_self = (DYN_DNS_CLIENT *) p_context;
	if (p_self == NULL)
	{
		return RC_INVALID_POINTER;
	}
    
    {
        DYNDNS_SYSTEM_INFO *it = get_dyndns_system_table();
        for (; it != NULL && it->id != LAST_DNS_SYSTEM; ++it)
        {
            if (strcmp(it->system.p_key, p_cmd->argv[current_nr]) == 0)
            {
                p_dns_system = &it->system;
            }
        }    
    }

    if (p_dns_system == NULL)
    {
        return RC_DYNDNS_INVALID_OPTION;
    }

    p_self->info.p_dns_system = p_dns_system;
	
	return RC_OK;
}

/** Read data from file into the given buffer.
	When the first separator is encountered it returns.
	Actions:
		- read chars while not eof
		- switch to DATA STATE if non space char is encountered
		- add data to buffer.
		- do not forget a 0 at the end.
*/	
static RC_TYPE read_option(FILE *p_file, char *p_buffer, int maxlen)
{
	RC_TYPE rc = RC_OK;
	BOOL state_data = FALSE;
	int count = 0;

    *p_buffer = 0;
	while(1)
	{
		char ch;
		int n = fscanf(p_file, "%c", &ch);
		if (n < 1)
		{	
            if (feof(p_file))
            {
                break;
            }
		    rc = RC_FILE_IO_READ_ERROR;		
			break;
		}
		if (!isspace(ch))
		{
			state_data = TRUE;
			if (count ++ < maxlen)
			{
				*p_buffer++ = ch;
			}
			else
   			{
   				rc = RC_FILE_IO_OUT_OF_BUFFER;
				break;
      		}		
		}
		else
		{
			if (state_data)
   			{
    			*p_buffer++ = 0;
   				break;
	  		}
		}
	}
	return rc;
}

/**
	This handler reads the data in the passed file name.
	Then appends the words in the table (cutting all spaces) to the existing cmd line options.
	It adds to the CMD_DATA struct.
	Actions:
		- open file
		- read characters and cut spaces away
		- add values one by one to the existing p_cmd data
*/
static RC_TYPE get_options_from_file_handler(CMD_DATA *p_cmd, int current_nr, void *p_context)
{
	RC_TYPE rc = RC_OK;
	DYN_DNS_CLIENT *p_self = (DYN_DNS_CLIENT *) p_context;
	FILE *p_file = NULL;
	char *p_tmp_buffer = NULL;
	const int buffer_size = DYNDNS_SERVER_NAME_LENGTH;
	
	if (!p_self || !p_cmd)
	{
		return RC_INVALID_POINTER;
	}
	
	do
	{	
 		p_tmp_buffer = malloc(buffer_size);
   		if (!p_tmp_buffer)
	 	{
	 		rc = RC_OUT_OF_MEMORY;
	 		break;
	  	}
	  	p_file = fopen(p_cmd->argv[current_nr], "r");	
	  	if (!p_file)
	  	{
	  		rc = RC_FILE_IO_OPEN_ERROR;
	  		break;
	  	}
    	while(!feof(p_file))
     	{		 		
	 		rc = read_option(p_file, p_tmp_buffer, buffer_size);
	 		if (rc != RC_OK)
	 		{
	 			break;
	 		}

            if (!strlen(p_tmp_buffer))
            {
                break;
            }

	 		rc = cmd_add_val(p_cmd, p_tmp_buffer);
	 		if (rc != RC_OK)
	 		{
	 			break;
	 		}
   		} 	     		
	}
	while(0);
	
	if (p_file)
	{
		fclose(p_file);
	}
	if (p_tmp_buffer)
	{
		free(p_tmp_buffer);
	}
	return rc;
}

/* 
	Set up all details:
		- ip server name
		- dns server name
		- username, passwd
		- ...
	Implementation:
		- load defaults
		- parse cmd line
        - assign settings that may change due to cmd line options
		- check data
*/
RC_TYPE get_config_data(DYN_DNS_CLIENT *p_self, int argc, char** argv)
{
	RC_TYPE rc = RC_OK;
	
	do
	{
		/*load default data */
		rc = get_default_config_data(p_self);
		if (rc != RC_OK)
		{	
			break;
		}
		/*set up the context pointers */
		{
			CMD_DESCRIPTION_TYPE *it = cmd_options_table;			
			while( it->p_option != NULL)
			{
				it->p_handler.p_context = (void*) p_self;
				++it;
			}
		}

		rc = get_cmd_parse_data(argv, argc, cmd_options_table);
		if (rc != RC_OK)
		{
			break;
		}	
        /*settings that may change due to cmd line options*/
        {
    		/*ip server*/
            if (strlen(p_self->info.ip_server_name.name) == 0)
            {
                if (sizeof(p_self->info.ip_server_name.name) < strlen(p_self->info.p_dns_system->p_ip_server_name))
                {
                    rc = RC_DYNDNS_BUFFER_TOO_SMALL;
                    break;
                }
                strcpy(p_self->info.ip_server_name.name, p_self->info.p_dns_system->p_ip_server_name);
            }
            
    		/*dyndns server*/
            if (strlen(p_self->info.dyndns_server_name.name) == 0)
            {
        		if (sizeof(p_self->info.dyndns_server_name.name) < strlen(p_self->info.p_dns_system->p_dyndns_server_name))
        		{
        			rc = RC_DYNDNS_BUFFER_TOO_SMALL;
        			break;
        		}
        		strcpy(p_self->info.dyndns_server_name.name, p_self->info.p_dns_system->p_dyndns_server_name);
            }
        }

		/*check if the neccessary params have been provided*/
		if ( 
			(strlen(p_self->info.dyndns_server_name.name) == 0)  ||
			(strlen(p_self->info.ip_server_name.name)	  == 0)  ||
			(p_self->alias_info.count == 0)
			)
		{
			rc = RC_DYNDNS_INVALID_OR_MISSING_PARAMETERS;
			break;
		}
		/*forced update*/
		p_self->times_since_last_update = 0;
		p_self->forced_update_times = p_self->forced_update_period_sec / p_self->sleep_sec;

	}
	while(0);

	if (rc != RC_OK)
	{
		print_help_page();
	}

	return rc;
}

