/******************************************************************************
 * $Id: freepops.c,v 1.37 2007/05/11 11:46:40 gareuselesinge Exp $
 * This file is part of FreePOPs (http://www.freepops.org)                    *
 * This file is distributed under the terms of GNU GPL license.               *
 ******************************************************************************/

/******************************************************************************
 * File description:
 *	The main function is here
 * Notes:
 * 	Options --kill added by Stefano Falsetto <falsetto@gnu.org>
 *
 * Authors:
 * 	Alessio Caprari <alessio.caprari@tiscali.it>
 * 	Nicola Cocchiaro <ncocchiaro@users.sourceforge.net>
 * 	Enrico Tassi <gareuselesinge@users.sourceforge.net>
 ******************************************************************************/

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <curl/curl.h>

#if !(defined(WIN32) && !defined(CYGWIN)) // && !defined(BEOS)
	#include <sys/types.h>
	#include <sys/stat.h>
	#include <netinet/in.h>
	#include <arpa/inet.h>
	#include <netdb.h>
	#include <signal.h>
	#include <grp.h>
	#include <pwd.h>
	#include <errno.h>
#endif

#if defined(WIN32) && !defined(CYGWIN)
	#include <winsock.h>
	#include "winsystray.h"
#endif

#ifdef MACOSX
	#include "getopt1.h"
#else
	#include <getopt.h>
#endif

#ifdef HAVE_CONFIG_H
#      include "config.h"
#endif

#if CRYPTO_IMPLEMENTATION == 1
	#include <pthread.h>
	#include <gcrypt.h>
#endif

#include "popserver.h"
#include "engine.h"
#include "altsocklib.h"
#include "regularexp.h"

#include "lua.h"
#include "luay.h"
#include "luabox.h"
#include "lauxlib.h"
#include "luay.h"

#include "log.h"
#define LOG_ZONE "INTERNAL"

#include "pid.h"

#define HIDDEN static

#define PLUGIN2XML "plugins2xml.lua"

/*** typedefs *****************************************************************/
typedef void (*sighandler_t)(int);

/*** globals set trough command line options *******************************/

int verbose_output=0;
int daemonize = 0;
char *logfile = NULL;
char **args = NULL;
int args_len = 0;

#if !(defined(WIN32) && !defined(CYGWIN)) && !defined(BEOS)
uid_t uid;
gid_t gid; 
#endif

/* extra arguement pool handling */
HIDDEN void add_to_args(const char * arg){
	args_len++;
	args = realloc(args,args_len * sizeof(void*));
	args[args_len - 1] = strdup(arg);
}

/*** usage ********************************************************************/
#define GETOPT_STRING "-b:p:P:A:u:t:l:s:dhVvwknx:e:"
HIDDEN  struct option opts[] = { { "bind", required_argument, NULL, 'b' },
				 { "port", required_argument, NULL, 'p' },
				 { "proxy", required_argument, NULL, 'P' },
				 { "auth", required_argument, NULL, 'A' },
				 { "useragent", required_argument, NULL, 'u' },
				 { "threads", required_argument, NULL, 't' },
				 { "help" , no_argument , NULL, 'h'},
				 { "version" , no_argument , NULL , 'V'},
				 { "verbose", no_argument, NULL, 'v' },
				 { "veryverbose", no_argument, NULL, 'w' },
				 { "logmode", required_argument, NULL, 'l' },
				 { "daemonize", no_argument, NULL, 'd' },
				 { "suid", required_argument, NULL, 's' },
				 { "kill", no_argument, NULL, 'k' },
				 { "no-pid-file", no_argument, NULL, 'n' },
				 { "toxml", required_argument, NULL, 'x' },
				 { "force-proxy-auth-type", 
					 required_argument, NULL, 1000 },
				 { "fpat", 
					 required_argument, NULL, 1000 },
				 { "no-icon",no_argument, NULL, 1001},
				 { "execute",required_argument, NULL, 'e'},
	                         { NULL, 0, NULL, 0 } };

HIDDEN void usage(const char *progname) {
	fprintf(stderr, 
"Usage:  %s\t[-b|--bind address] \n"
"\t\t\t[-p|--port portnumber] \n"
"\t\t\t[-P|--proxy proxyaddress:proxyport] \n"
"\t\t\t[-A|--auth username:password] \n"
"\t\t\t[-u|--useragent useragent] \n"
"\t\t\t[-v|--verbose [-v| --verbose]]\n"
"\t\t\t[-w|--veryverbose]\n"
"\t\t\t[-t|--threads num]\n"
"\t\t\t[-d|--daemonize]\n"
"\t\t\t[-l|--logmode (syslog|filename|stdout)]\n"
"\t\t\t[-x|--toxml file]\n"
"\t\t\t[-e|--execute scriptfile [args...]]\n"
"\t\t\t[--fpat|--force-proxy-auth-type (basic|digest|ntlm|gss)]\n"
#if defined(WIN32)
"\t\t\t[--no-icon]\n"
#endif
#if !defined(WIN32) && !defined(BEOS)			
"\t\t\t[-s|--suid user.group]\n"
"\t\t\t[-k|--kill]\n"
"\t\t\t[-n|--no-pid-file]\n"
#endif
"        %s\t[-V|--version]\n"
"        %s\t[-h|--help]\n\n", progname, progname, progname);
}

/*** WIN32 only functions *****************************************************/
#if defined(WIN32) && !defined(CYGWIN)

HIDDEN void win32_init(int *argc,char ***argv,LPSTR lpszCmdLine) {
	char *lastslash;
	  
	if(stderr != freopen("stderr.txt","w",stderr)) 
		fprintf(stderr,"Unable to redirect stderr\n");

	if(stdout != freopen("stdout.txt","w",stdout)) 
		fprintf(stderr,"Unable to redirect stdout\n");		
	*argc = parse_commandline(argv, lpszCmdLine);

	/* Try to change working directory if command line provides a path */
	if ((lastslash = strrchr((*argv)[0], '\\')) != NULL) {
		char *dir;
		int length = lastslash - (*argv)[0] + 2;
		if (length <= MAX_PATH &&
		    (dir = (char *)calloc(length, sizeof(char))) != NULL) {
			strncpy(dir, (*argv)[0], length - 1);
			dir[length - 1] = '\0';
			SetCurrentDirectory(dir);
			free(dir);
		}
	}

	sockinit();
}

HIDDEN void win32_exit(){
	fclose(stdout);
	fclose(stderr);
}
#endif

/*** unix only functions ******************************************************/
#if !(defined(WIN32) && !defined(CYGWIN)) && !defined(BEOS)
HIDDEN int *sighandler(int sig){
	if (sig == SIGINT || sig == SIGTERM) {
		remove_pid_file();
		SAY("%s killed by %d\n\n",PROGRAMNAME,sig);
		LOG_END();
		exit(0);
	} else {
		SAY("what?\n");
	}

	return 0;
}

HIDDEN void daemonize_process(){
	if(fork()!=0)
		{
		exit(0);
		}

	setsid();

	setpgid(0, 0);

	close(0);
	if(logfile != NULL && !strcmp(logfile,"stdout") && verbose_output != 0)
		{
		SAY("Can't log to %s and daemonize!\n",logfile);
		ERROR_ABORT("Bailing out!\n");
		}
	close(1);
	close(2);
}

HIDDEN int loose_rights(uid_t set_uid,gid_t set_gid){
	int rc=0;

	rc = setegid(set_gid);
	if(rc == -1)
		{
		SAY("Unable to setegid(%d)",set_gid);
		return 1;	
		}

	rc = seteuid(set_uid);
	if(rc == -1)
		{
		SAY("Unable to seteuid(%d)",set_uid);
		return 1;	
		}

	rc = setregid(getegid(),getegid());
	if(rc == -1)
		{
		SAY("Unable to setregid(%d)",getegid(),getegid());
		return 1;	
		}

	rc = setreuid(geteuid(),geteuid());
	if(rc == -1)
		{
		SAY("Unable to setreuid(%d,%d)",geteuid(),geteuid());
		return 1;	
		}

	return rc;
}

HIDDEN void set_signals(){
	// signal for ctrl+c 	
	signal(SIGINT,  (sighandler_t) sighandler);
	// signal for debian start-stop-daemon
	signal(SIGTERM, (sighandler_t) sighandler);

	//probably needed only by MACOSX and FreeBSD systems
	signal(SIGPIPE,SIG_IGN);
}

HIDDEN void parse_suid(const char* optarg){
	int rc;

	if(optarg == NULL)
		fprintf(stderr,"%s : %s : %d : optarg cant be NULL\n"
			,__FILE__,__FUNCTION__,__LINE__);

	//fprintf(stderr,"optarg = ^%s$\n",optarg);

	if( (rc = regfind_start(optarg,"^[A-Za-z_]+\\.[A-Za-z_]+$")) != -1 )
		{
		//alphabetic form
		struct passwd *pw;
		struct group *gr;
		char *tmp = strdup(optarg);

		rc = regfind_end(tmp,"^[A-Za-z_]+\\.");
		rc--;

		tmp[rc] = '\0';
		pw = getpwnam(tmp);
		if(pw == NULL)
			{
			fprintf(stderr,"Unable to getpwnam(\"%s\")\n",tmp);
			fprintf(stderr,"getpwnam: %s\n",strerror(errno));
			goto error;
			}
		uid = pw->pw_uid;

		rc++;
		gr = getgrnam(&tmp[rc]);
		if(gr == NULL)
			{
			fprintf(stderr,"Unable to getgrnam(\"%s\")\n",&tmp[rc]);
			fprintf(stderr,"getgrnam: %s\n",strerror(errno));
			goto error;
			}
		gid = gr->gr_gid;

		//fprintf(stderr,"uid = %d gid = %d\n",uid,gid);
		
		free(tmp);
		}
	else if ( (rc = regfind_start(optarg,"^[0-9]+\\.[0-9]+$")) != -1 )
		{
		//numeric form
		char *tmp = strdup(optarg);

		rc = regfind_end(tmp,"^[0-9]+\\.");
		rc--;

		tmp[rc] = '\0';
		uid = strtol(tmp,NULL,10);
		if(errno == ERANGE)
			goto error;			
		//printf("-> ^%s$\n",tmp);

		rc++;
		gid = strtol(&tmp[rc],NULL,10);
		if(errno == ERANGE)
			goto error;	
		//printf("-> ^%s$\n",&tmp[rc]);
		
		//fprintf(stderr,"uid = %d gid = %d\n",uid,gid);
		
		free(tmp);	
		}
	else
		goto error;

	return;

	error:
		fprintf(stderr,"Invalid parameter for -s --suid.\n");
		fprintf(stderr,"usage: -s username.group\n");
		perror("bailing out");
		exit(1);	
}

#endif


/*** helpers ******************************************************************/
HIDDEN void start_logging(char* logfile,int verbosity) {
	log_set_verbosity(verbosity);
	LOG_INIT(logfile);
	SAY("freepops started with loglevel %d on a %s machine.\n",verbosity,
		((unsigned short)1 != htons(1))?"little endian":"big endian");
}
/*** LUA INTERPRETER BOOTSTRAPPING (for using FP as luafull) *****************/

/* start the interpreter */
HIDDEN int execute(const char* scriptfile, const char* stdoutname){
	int rc, e;
	
	// boot
	lua_State* l = bootstrap(NULL,NULL);
	if(l == NULL){
		ERROR_PRINT("Unable to bootstrap\n");
		return -1;
	}
	
	// load the script 
	e = luay_call(l, "s|b", "freepops.dofile", scriptfile, &rc);
	if(!rc || e){
		ERROR_PRINT("Unable to load %s\n",scriptfile);
		return -1;
	}
	
	// begin redirection
	if (stdoutname != NULL) {
		if(stdout != freopen(stdoutname,"w",stdout)) {
			fprintf(stderr,"Unable to redirect stdout to %s\n",
					stdoutname);	
		}
	}
	
	// main 
	e = luay_callv(l, "|d", "main", args, args_len , &rc);
	if (e) {
		fprintf(stderr,"The main function raised an error\n");
		luay_printstack(l);
		return e;
	}
	// end redirection 
	if (stdoutname != NULL) {
		fclose(stdout);
	}
	
	return rc;
}

void my_putenv(const char* a, const char* b) {
	char * tmp = calloc(1024,sizeof(char));

	if(b == NULL)
		return;

	snprintf(tmp,1024,"%s=%s",a,b);

	/* on some system this is not a proper leak since tmp is used (not
	 * copyed) in the environment.
	 *
	 * valgrind says this is the correct thing to do, so I prefer calling
	 * this memory leak function only in the main, outside loops. so, even
	 * memory is "lost" it is not a memory leak.
	 */
	putenv(tmp);
}

/*** THE MAIN HAS YOU *********************************************************/
#if CRYPTO_IMPLEMENTATION == 1
	GCRY_THREAD_OPTION_PTHREAD_IMPL;
#endif

#if !(defined(WIN32) && !defined(CYGWIN))
int main(int argc, char *argv[]) {
#else
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow){
#endif
	int res;
	int threads_num = MAXTHREADS;
	unsigned short port = POP3PORT;
	struct in_addr address;
	char *useragent = NULL, *proxy = NULL, *proxyauth = NULL, *fpat = NULL;
	char *script = NULL, *execute_stdout = NULL;

#if defined(WIN32)	
	int tray_icon = 1;
#endif

#if !(defined(WIN32) && !defined(CYGWIN)) && !defined(BEOS)	
        pid_t this_pid;
	int no_pid = 0; /* by default we want the pid file */
#endif

#if defined(WIN32) && !defined(CYGWIN)
	int argc;
	char **argv;

	win32_init(&argc,&argv,lpszCmdLine);
#endif


#if CRYPTO_IMPLEMENTATION == 1
       if (!gcry_check_version("1.2.2")) {
	       fprintf(stderr, "gcrypt version older than 1.2.2\n");
	       exit(1);
	}
	gcry_control(GCRYCTL_SET_THREAD_CBS,&gcry_threads_pthread);
	gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
#endif
	
	curl_global_init(CURL_GLOBAL_ALL);
	
#if !(defined(WIN32) && !defined(CYGWIN)) && !defined(BEOS)	
	uid = geteuid();
	gid = getegid();
#endif

	address.s_addr = htonl(BINDADDRESS);
	logfile = strdup(LOGFILE); //means stdout
	
/*** ARGUMENTS PARSING ***/
	while (script == NULL && /* all arguments left are for the script*/
	(res=getopt_long(argc,argv,GETOPT_STRING,opts,NULL))!= -1){
		if (res == 'p') {
			/* --port */
#if defined(MACOSX)
			/* ignore .app parameter */
			if (strncmp(optarg, "sn_", 3))
			{
#endif
			if ((port = atoi(optarg)) == 0) {
				fprintf(stderr, "Invalid port number\n");
				usage(argv[0]);
				exit(1);
			}
#if defined(MACOSX)
			}
#endif
		} else if (res == 'b') {
			/* --bind */
			struct hostent *host;
			
			host = gethostbyname(optarg);
			if (host == NULL) {
				usage(argv[0]);
				exit(1);
			} else {
				address = *(struct in_addr*)(host->h_addr);
			}
		} else if (res == 'P') {
			/* --proxy */
			if (proxy != NULL) {
				usage(argv[0]);
				exit(1);
			}
			proxy = strdup(optarg);
		} else if (res == 'A') {
			/* --auth */
			if (proxyauth != NULL) {
				//usage(argv[0]);
				
				exit(1);
			}
			proxyauth = strdup(optarg);
		} else if (res == 'u') {
			/* --useragent */
			if (useragent != NULL) {
				usage(argv[0]);
				exit(1);
			}
			useragent = strdup(optarg);
		} else if (res == 't'){
			/*--threads */
			if ((threads_num = atoi(optarg)) == 0) {
				fprintf(stderr, 
					"Invalid max-threads-number number\n");
				usage(argv[0]);
				exit(1);
			}
		} else if (res == 'V') {
			/* --version */
			fprintf(stderr, "%s %s\n",
				PROGRAMNAME,VERSION);
			return 0;
		} else if (res == 'h') {	
			/* --help */
			usage(argv[0]);
			return 0;
		} else if (res == 'v') {
			/* --verbose */
			verbose_output++;
		} else if (res == 'l') {
			/* --logmode */
			free(logfile);
			if ((optarg != NULL) && (optarg[0] == '-'))
				fprintf(stderr, "Warning: using %s as logfile"
					" name, which is probably not what"
					" you want.\n", optarg);
			logfile = strdup(optarg);
		} else if (res == 'd') {
			/* --daemonize */
			daemonize = 1;
			free(logfile);
			logfile = NULL; 
		} else if (res == 'w') {
			/* --veryverbose */
			verbose_output+=2;
	#if defined(WIN32)
		} else if (res == 1001) {
			tray_icon = 0;		
	#endif
		} else if (res == 1000) {
			/* --fpat */
			free(fpat);
			
			if (optarg != NULL)
				fpat = strdup(optarg);
			else
				fprintf(stderr,"fpat has NULL arg");
			
			if ( strcmp(fpat,"gss") && 
			     strcmp(fpat,"ntlm") &&
			     strcmp(fpat,"basic") && 
			     strcmp(fpat,"digest") ) {
				fprintf(stderr, "--fpath accepts only one of"
					"these: gss, ntlm, basic, digest\n\n");
				usage(argv[0]);
				exit(1);
			}
	#if !(defined(WIN32) && !defined(CYGWIN)) && !defined(BEOS)
		} else if (res == 'k') {
			/* --kill */
			/* Kill freepopsd with pid contained in .pid file */
			this_pid = retrieve_pid_file(PIDFILE);
			if (this_pid != PIDERROR) {
				kill((pid_t)this_pid, SIGINT);
                                exit(0);
                        } else {
				printf("Warning: can't find a pid file.\n");
				exit(1);
			}
		} else if (res == 's') {
			/* --suid */
			parse_suid(optarg);
		} else if (res == 'n') {
			/* --no-pid-file */
			no_pid = 1;
	#endif			
		} else if (res == 'x') {
			/* --toxml */
			int i;
		#ifdef WIN32
			int len = strlen(optarg) + 5;
			char *outname = calloc(len,sizeof(char));
			if ( outname == NULL) {
				fprintf(stderr,"Unable to malloc\n");
				exit(1);
			}
			snprintf(outname,len,"%s.xml",optarg);
			free(execute_stdout);
			execute_stdout=outname;
		#endif			
			free(script);
			script=strdup(PLUGIN2XML);
			add_to_args(optarg);
			for (i=optind;i<argc;i++){
				add_to_args(argv[i]);
			}
		} else if (res == 'e'){
			/* --execute */
			int i;
			free(script);
			script=strdup(optarg);
			free(execute_stdout);
			execute_stdout=NULL;
			for (i=optind;i<argc;i++){
				add_to_args(argv[i]);
			}
		} else if (res == 1){
			/* extra arguments */
			add_to_args(optarg);
		} else {
			/* unknown param */
			usage(argv[0]);
			exit(1);
		}
	}
	
/*** INITIALIZATION ALL ***/
	
	srand(time(NULL) + getpid());
	
	start_logging(logfile,verbose_output);
	
	if(useragent == NULL)
		useragent = strdup(DEFAULT_USERAGENT);
	
	my_putenv("LUA_HTTP_USERAGENT",useragent);
	my_putenv("LUA_HTTP_PROXY",proxy);
	my_putenv("LUA_HTTP_PROXYAUTH",proxyauth);
	my_putenv("LUA_FORCE_PROXY_AUTH_TYPE",fpat);
	
/*** INTERPRETER MODE ***/
	if (script != NULL){
		exit(execute(script,execute_stdout));
	}
	
/*** INITIALIZATION UNIX ***/
#if !(defined(WIN32) && !defined(CYGWIN)) && ! defined(BEOS)
	if(daemonize)
		daemonize_process();

	if ( ! no_pid ) 
		create_pid_file(PIDFILE);
	
	set_signals();
#endif

/*** INITIALIZATION WIN32 ***/
#if defined(WIN32) && !defined(CYGWIN)
	if(tray_icon)
		create_tray_icon(hInstance,hPrevInstance,lpszCmdLine,nCmdShow);
#endif

/*** GO! ***/
#if !(defined(WIN32) && !defined(CYGWIN)) && ! defined(BEOS)
	popserver_start(&freepops_functions, address, port, threads_num,
		loose_rights,uid,gid);
#else
	popserver_start(&freepops_functions, address, port, threads_num,
		NULL,0,0);
#endif
	
/*** EXIT ***/
#if defined(WIN32) && !defined(CYGWIN)
	win32_exit();
#endif

	return EXIT_SUCCESS;
}


