/* Copyright (c) 1995,1996,1997 NEC Corporation.  All rights reserved.       */
/*                                                                           */
/* The redistribution, use and modification in source or binary forms of     */
/* this software is subject to the conditions set forth in the copyright     */
/* document ("Copyright") included with this distribution.                   */

/*
 * $Id: daemon.c,v 1.62.2.1.2.5 1998/07/19 22:35:46 wlu Exp $
 */

/* This file has the main function in it for socks5, as well as variables    */
/* which most or several modules will use...                                 */
#include "socks5p.h"
#include "threads.h"
#include "daemon.h"
#include "socket.h"
#include "protocol.h"
#include "msgids.h"
#include "log.h"


#define MAXTHREADS 32

#ifndef NUMCLIENTS
#define NUMCLIENTS 64
#endif

#ifndef HAVE_SETSID
#ifdef  HAVE_SETPGID
#define setsid() setpgid(0, getpid())
#elif defined(SETPGRP_TWOARG)
#define setsid() setpgrp(0, getpid())
#else
#define setsid() setpgrp()
#endif
#endif

MUTEX_T lt_mutex  = MUTEX_INITIALIZER; /* localtime mutex    */
MUTEX_T accept_mutex  = MUTEX_INITIALIZER; /* localtime mutex    */
MUTEX_T env_mutex = MUTEX_INITIALIZER; /* *env mutex         */
MUTEX_T gpw_mutex = MUTEX_INITIALIZER; /* getpwd* mutex      */
MUTEX_T gh_mutex  = MUTEX_INITIALIZER; /* gethost* mutex     */
MUTEX_T gs_mutex  = MUTEX_INITIALIZER; /* getserv* mutex     */

int nthreads   = 0;
int nservers   = 0;
int isthreaded = 0;
int servermode = 0;
int idletimeout = 15;
char *bindif   = NULL;
u_short ludpport = 0;
u_short hudpport = 0xffff;

static void version(void) {
    fprintf(stdout, "Socks5 version: %s", SOCKS5_VERSION_NAME);
    exit(0);
}

static void usage(void) {
    fprintf(stderr, "Usage incorrect...\n");
    fprintf(stderr, "usage: socks5 ");
    fprintf(stderr, "[-d [X]|--debug [X]] ");
    fprintf(stderr, "[-s|--stderr] ");
    fprintf(stderr, "[-v|--version] ");
    fprintf(stderr, "\n");
    fprintf(stderr, "[-b X|--bindintfc X] ");
    fprintf(stderr, "[-f|--foreground] ");
    fprintf(stderr, "[-i|--inetd] ");
    fprintf(stderr, "[-p|--prefork] ");
    fprintf(stderr, "[-o|--oneshot] ");
    fprintf(stderr, "[-t|--threaded] ");
    fprintf(stderr, "[-n X|--nchildren X]");
    fprintf(stderr, "\n");
    exit(-1);
}

int main(int argc, char **argv, char **envp) {
    char tbuf[1024];
    int how = S5_LOG_SYSTEM, level = S5_LOG_INFO;
    time_t now = time(NULL);
    int separate = 1;

    IFTHREADED(MUTEX_SETUP(accept_mutex);)
    IFTHREADED(MUTEX_SETUP(env_mutex);)
    IFTHREADED(MUTEX_SETUP(gpw_mutex);)
    IFTHREADED(MUTEX_SETUP(gh_mutex);)
    IFTHREADED(MUTEX_SETUP(gs_mutex);)
    IFTHREADED(MUTEX_SETUP(lt_mutex);)

    for (argc--, argv++; argc > 0; argc--, argv++) {
	if (!strncmp(*argv, "-d", strlen("-d")) || !strncmp(*argv, "--debug", strlen("--debug")))  {
            if (*(argv+1) && isdigit((int)**(argv+1))) {
                level = S5_LOG_DEBUG(atoi(*++argv)); argc--;
            } else level = S5_LOG_DEBUG(25);
	} else if (!strcmp(*argv, "-s") || !strcmp(*argv, "--stderr"))   {
	    how = S5_LOG_LOCAL;
	} else if (!strcmp(*argv, "-i") || !strcmp(*argv, "--inetd"))  {
	    servermode = INETD;
	} else if (!strcmp(*argv, "-f") || !strcmp(*argv, "--foreground")) {
	    separate = 0;
	} else if (!strcmp(*argv, "-p") || !strcmp(*argv, "--prefork")) {
	    servermode = PREFORKING;
	} else if (!strcmp(*argv, "-n") || !strcmp(*argv, "--nchildren")) {
            if (!(*++argv) || !isdigit(**argv)) usage();
            else {
                nservers = atoi(*argv); argc--;
            }
	} else if (!strcmp(*argv, "-b") || !strcmp(*argv, "--bindintfc")) {
            if (!(*++argv)) usage();
            else {
                bindif = strdup(*argv); argc--;
            }
	} else if (!strcmp(*argv, "-v") || !strcmp(*argv, "--version"))   {
	    version();
	} else if (!strcmp(*argv, "-t") || !strcmp(*argv, "--threaded"))  {
	    S5LogShowThreadIDS = 1;
	    servermode = THREADED;
	} else if (!strcmp(*argv, "-o") || !strcmp(*argv, "--oneshot"))   {
	    servermode = SINGLESHOT;
	    level      = S5_LOG_DEBUG_MAX;
	    how        = S5_LOG_LOCAL;
	    separate   = 0;
	} else {
	    usage();
	}
    }

    if (separate && fork() > 0) exit(0);
    chdir("/");
    umask(0);
    setsid();

    nthreads = MIN(MAXOPENS/4 - 1, MAXTHREADS);
    if (nservers == 0) {
	if (servermode == THREADED) nservers = NUMCLIENTS/4;
	else nservers = MIN(MAXCLIENTS, NUMCLIENTS);
    }

#ifdef RLIMIT_NOFILE
    if (servermode == THREADED) {
	struct rlimit rl;
	
        /* try allow for maxc open file descriptors                          */
	if (getrlimit(RLIMIT_NOFILE, &rl) == 0) {
	    if (rl.rlim_cur < rl.rlim_max) {
	    	rl.rlim_cur = rl.rlim_max;
#ifndef bsdi
	    	setrlimit(RLIMIT_NOFILE, &rl);
#endif
	    }

	    nthreads = MIN(rl.rlim_cur/4 - 1, MAXTHREADS);
	}
    }
#endif

#ifdef RLIMIT_NPROC
    if (servermode != INETD) {
	struct rlimit rl;
	
	/* try allow for maxc children (or MAXCLIENTS)                       */
	if (getrlimit(RLIMIT_NPROC, &rl) == 0 && nservers > rl.rlim_cur && nservers <= rl.rlim_max) {
	    rl.rlim_cur = nservers;
	    setrlimit(RLIMIT_NPROC, &rl);
	}
    }
#endif

    S5LogStart(&S5LogDefaultHandle, how, level, "Socks5");

    /* make sure that number of child processes will not exceed the system    */
    /* limit...                                                               */
    if (nservers > MAXCLIENTS) {
    	S5LogUpdate(S5LogDefaultHandle, S5_LOG_INFO, MSGID_SERVER_START, "Requested child process number (%d). Socks5 will use the system limit (%d)", nservers, MAXCLIENTS);
	nservers = MAXCLIENTS;
    }


    LIBPREFIX2(init)("Socks5");

    MUTEX_LOCK(lt_mutex);
    strftime(tbuf, sizeof(tbuf), "%c", localtime(&now));
    MUTEX_UNLOCK(lt_mutex);
    switch (servermode) {
	case NORMAL:
	    strcat(tbuf, " in normal mode");      break;
	case INETD:
	    strcat(tbuf, " from inetd");          break;
	case PREFORKING:
	    strcat(tbuf, " in preforking mode");  break;
	case SINGLESHOT:
	    strcat(tbuf, " in single shot mode"); break;
	case THREADED:
	    strcat(tbuf, " in threading mode");   break;
	default:
	    strcat(tbuf, " in unknown mode");     break;
    }
    S5LogUpdate(S5LogDefaultHandle, S5_LOG_INFO, MSGID_SERVER_START, "Socks5 starting at %s", tbuf);

    GetConnection();
    return(-1);
}

