/*
 * $Id: main.c 2304 2007-05-25 16:36:07Z bogdan_iancu $
 *
 * Copyright (C) 2001-2003 FhG Fokus
 * Copyright (C) 2005-2006 Voice Sistem S.R.L.
 *
 * This file is part of openser, a free SIP server.
 *
 * openser 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
 *
 * openser 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
 *
 * History:
 * -------
 *  2002-01-29  argc/argv globalized via my_{argc|argv} (jiri)
 *  2003-01-23  mhomed added (jiri)
 *  2003-03-19  replaced all malloc/frees w/ pkg_malloc/pkg_free (andrei)
 *  2003-03-29  pkg cleaners for fifo and script callbacks introduced (jiri)
 *  2003-03-31  removed snmp part (obsolete & no place in core) (andrei)
 *  2003-04-06  child_init called in all processes (janakj)
 *  2003-04-08  init_mallocs split into init_{pkg,shm}_mallocs and 
 *               init_shm_mallocs called after cmd. line parsing (andrei)
 *  2003-04-15  added tcp_disable support (andrei)
 *  2003-05-09  closelog() before openlog to force opening a new fd 
 *              (needed on solaris) (andrei)
 *  2003-06-11  moved all signal handlers init. in install_sigs and moved it
 *              after daemonize (so that we won't catch anymore our own
 *              SIGCHLD generated when becoming session leader) (andrei)
 *              changed is_main default value to 1 (andrei)
 *  2003-06-28  kill_all_children is now used instead of kill(0, sig)
 *              see comment above it for explanations. (andrei)
 *  2003-06-29  replaced port_no_str snprintf w/ int2str (andrei)
 *  2003-10-10  added switch for config check (-c) (andrei)
 *  2003-10-24  converted to the new socket_info lists (andrei)
 *  2004-03-30  core dump is enabled by default
 *              added support for increasing the open files limit    (andrei)
 *  2004-04-28  sock_{user,group,uid,gid,mode} added
 *              user2uid() & user2gid() added  (andrei)
 *  2004-09-11  added timeout on children shutdown and final cleanup
 *               (if it takes more than 60s => something is definitely wrong
 *                => kill all or abort)  (andrei)
 *              force a shm_unlock before cleaning-up, in case we have a
 *               crashed childvwhich still holds the lock  (andrei)
 *  2004-12-02  removed -p, extended -l to support [proto:]address[:port],
 *               added parse_phostport, parse_proto (andrei)
 *  2005-06-16  always record the pid in pt[process_no].pid twice: once in the
 *               parent & once in the child to avoid a short window when one
 *               of them might use it "unset" (andrei)
 *  2005-12-22  added tos configurability (thanks to Andreas Granig)
 *  2006-04-26  2-stage TLS init: before and after config file parsing (klaus)
 */


#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <string.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <sys/utsname.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <pwd.h>
#include <grp.h>
#include <signal.h>
#include <time.h>

#include <sys/ioctl.h>
#include <net/if.h>
#ifdef HAVE_SYS_SOCKIO_H
#include <sys/sockio.h>
#endif

#include "config.h"
#include "dprint.h"
#include "daemonize.h"
#include "route.h"
#include "udp_server.h"
#include "globals.h"
#include "mem/mem.h"
#ifdef SHM_MEM
#include "mem/shm_mem.h"
#endif
#include "sr_module.h"
#include "timer.h"
#include "parser/msg_parser.h"
#include "ip_addr.h"
#include "resolve.h"
#include "parser/parse_hname2.h"
#include "parser/digest/digest_parser.h"
#include "unixsock_server.h"
#include "name_alias.h"
#include "hash_func.h"
#include "pt.h"
#include "script_cb.h"
#include "blacklists.h"

#include "ut.h"
#include "serialize.h"
#include "statistics.h"
#include "items.h"
#ifdef USE_TCP
#include "poll_types.h"
#include "tcp_init.h"
#ifdef USE_TLS
#include "tls/tls_init.h"
#endif
#endif
#include "version.h"
#include "mi/mi_core.h"

static char id[]="@(#) $Id: main.c 2304 2007-05-25 16:36:07Z bogdan_iancu $";
static char* version=SER_FULL_VERSION;
static char* flags=SER_COMPILE_FLAGS;
char compiled[]= __TIME__ " " __DATE__ ;


static char help_msg[]= "\
Usage: " NAME " -l address [-p port] [-l address [-p port]...] [options]\n\
Options:\n\
    -f file      Configuration file (default " CFG_FILE ")\n\
    -c           Check configuration file for errors\n\
    -C           Similar to '-c' but in addition checks the flags of exported\n\
                  functions from included route blocks\n\
    -l address   Listen on the specified address/interface (multiple -l\n\
                  mean listening on more addresses).  The address format is\n\
                  [proto:]addr[:port], where proto=udp|tcp and \n\
                  addr= host|ip_address|interface_name. E.g: -l locahost, \n\
                  -l udp:127.0.0.1:5080, -l eth0:5062 The default behavior\n\
                  is to listen on all the interfaces.\n\
    -n processes Number of child processes to fork per interface\n\
                  (default: 8)\n\
    -r           Use dns to check if is necessary to add a \"received=\"\n\
                  field to a via\n\
    -R           Same as `-r` but use reverse dns;\n\
                  (to use both use `-rR`)\n\
    -v           Turn on \"via:\" host checking when forwarding replies\n\
    -d           Debugging mode (multiple -d increase the level)\n\
    -D           Do not fork into daemon mode\n\
    -E           Log to stderr\n"
#ifdef USE_TCP
"    -T           Disable tcp\n\
    -N processes Number of tcp child processes (default: equal to `-n`)\n\
    -W method    poll method\n"
#endif
"    -V           Version number\n\
    -h           This help message\n\
    -b nr        Maximum receive buffer size which will not be exceeded by\n\
                  auto-probing procedure even if  OS allows\n\
    -m nr        Size of shared memory allocated in Megabytes\n\
    -w dir       Change the working directory to \"dir\" (default \"/\")\n\
    -t dir       Chroot to \"dir\"\n\
    -u uid       Change uid \n\
    -g gid       Change gid \n\
    -P file      Create a pid file\n\
    -G file      Create a pgid file\n\
    -x socket    Create a unix domain socket \n"
;

/* print compile-time constants */
void print_ct_constants()
{
#ifdef ADAPTIVE_WAIT
	printf("ADAPTIVE_WAIT_LOOPS=%d, ", ADAPTIVE_WAIT_LOOPS);
#endif
/*
#ifdef SHM_MEM
	printf("SHM_MEM_SIZE=%d, ", SHM_MEM_SIZE);
#endif
*/
	printf("MAX_RECV_BUFFER_SIZE %d, MAX_LISTEN %d,"
			" MAX_URI_SIZE %d, BUF_SIZE %d\n",
		MAX_RECV_BUFFER_SIZE, MAX_LISTEN, MAX_URI_SIZE, 
		BUF_SIZE );
#ifdef USE_TCP
	printf("poll method support: %s.\n", poll_support);
#endif
	printf("svnrevision: %s\n", 
#ifdef SVNREVISION
	SVNREVISION
#else
	"unknown"
#endif
	);
}


/* global vars */

int own_pgid = 0; /* whether or not we have our own pgid (and it's ok
					 to use kill(0, sig) */
char* cfg_file = 0;
unsigned int maxbuffer = MAX_RECV_BUFFER_SIZE; /* maximum buffer size we do
												  not want to exceed during the
												  auto-probing procedure; may 
												  be re-configured */
int children_no = 0;			/* number of children processing requests */
#ifdef USE_TCP
int tcp_children_no = 0;
int tcp_disable = 0; /* 1 if tcp is disabled */
#endif
#ifdef USE_TLS
int tls_disable = 1; /* 1 if tls is disabled */
#endif
struct process_table *pt=0;		/*array with children pids, 0= main proc,
									alloc'ed in shared mem if possible*/
int sig_flag = 0;              /* last signal received */
int debug = L_NOTICE;
int dont_fork = 0;
int log_stderr = 0;
/* log facility (see syslog(3)) */
int log_facility = LOG_DAEMON;
/* the id to be printed in syslog */
char *log_name = 0;
int config_check = 0;
/* check if reply first via host==us */
int check_via =  0;        
/* debugging level for memory stats */
int memlog = L_DBG;
/* should replies include extensive warnings? by default yes,
   good for trouble-shooting
*/
int sip_warning = 0;
/* should localy-generated messages include server's signature?
   be default yes, good for trouble-shooting
*/
int server_signature=1;
/* Server header to be used when proxy generates request as UAS.
   Default is to use SERVER_HDR CRLF (assigned later).
*/
str server_header = {SERVER_HDR,sizeof(SERVER_HDR)-1};
/* User-Agent header to be used when proxy generates request as UAC.
   Default is to use USER_AGENT CRLF (assigned later).
*/
str user_agent_header = {USER_AGENT,sizeof(USER_AGENT)-1};
/* should openser try to locate outbound interface on multihomed
 * host? by default not -- too expensive
 */
int mhomed=0;
/* use dns and/or rdns or to see if we need to add 
   a ;received=x.x.x.x to via: */
int received_dns = 0;      
char* working_dir = 0;
char* chroot_dir = 0;
char* user=0;
char* group=0;
int uid = 0;
int gid = 0;
char* sock_user=0;
char* sock_group=0;
int sock_uid= -1;
int sock_gid= -1;
int sock_mode= S_IRUSR| S_IWUSR| S_IRGRP| S_IWGRP; /* rw-rw---- */

/* more config stuff */
int disable_core_dump=0; /* by default enabled */
int open_files_limit=-1; /* don't touch it by default */
/* a hint to reply modules whether they should send reply
   to IP advertised in Via or IP from which a request came
*/
int reply_to_via=0;

#ifdef USE_MCAST
int mcast_loopback = 0;
int mcast_ttl = -1; /* if -1, don't touch it, use the default (usually 1) */
#endif /* USE_MCAST */

int tos = IPTOS_LOWDELAY;

struct socket_info* udp_listen=0;
#ifdef USE_TCP
struct socket_info* tcp_listen=0;
#endif
#ifdef USE_TLS
struct socket_info* tls_listen=0;
#endif
struct socket_info* bind_address=0; /* pointer to the crt. proc.
									 listening address*/
struct socket_info* sendipv4; /* ipv4 socket to use when msg. comes from ipv6*/
struct socket_info* sendipv6; /* same as above for ipv6 */
#ifdef USE_TCP
struct socket_info* sendipv4_tcp; 
struct socket_info* sendipv6_tcp; 
#endif
#ifdef USE_TLS
struct socket_info* sendipv4_tls;
struct socket_info* sendipv6_tls;
#endif

/* if aliases should be automatically discovered and added 
 * during fixing listening sockets */
int auto_aliases=1;

unsigned short port_no=0; /* default port*/
#ifdef USE_TLS
unsigned short tls_port_no=0; /* default port */
#endif

struct host_alias* aliases=0; /* name aliases list */

/* ipc related globals */
int process_no = 0;
/* process_bm_t process_bit = 0; */
#ifdef ROUTE_SRV
#endif

/* cfg parsing */
int cfg_errors=0;

/* shared memory (in MB) */
unsigned long shm_mem_size=SHM_MEM_SIZE * 1024 * 1024;

/* export command-line to anywhere else */
int my_argc;
char **my_argv;

#define MAX_FD 32 /* maximum number of inherited open file descriptors,
		    (normally it shouldn't  be bigger  than 3) */


extern FILE* yyin;
extern int yyparse();


int is_main=1; /* flag = is this the  "main" process? */

char* pid_file = 0; /* filename as asked by user */
char* pgid_file = 0;


/* call it before exiting; if show_status==1, mem status is displayed */
void cleanup(int show_status)
{
	/*clean-up*/
	if (mem_lock) 
		shm_unlock(); /* hack: force-unlock the shared memory lock in case
					 some process crashed and let it locked; this will 
					 allow an almost gracious shutdown */
	destroy_modules();
#ifdef USE_TCP
	destroy_tcp();
#endif
#ifdef USE_TLS
	destroy_tls();
#endif
	destroy_timer();
	close_unixsock_server();
	destroy_stats_collector();
	destroy_script_cb();
	xl_free_extra_spec();
	destroy_black_lists();
#ifdef PKG_MALLOC
	if (show_status){
		LOG(memlog, "Memory status (pkg):\n");
		pkg_status();
	}
#endif
#ifdef SHM_MEM
	if (pt) shm_free(pt);
	pt=0;
	if (show_status){
			LOG(memlog, "Memory status (shm):\n");
			shm_status();
	}
	/* zero all shmem alloc vars that we still use */
	shm_mem_destroy();
#endif
	if (pid_file) unlink(pid_file);
	if (pgid_file) unlink(pgid_file);
}


/* tries to send a signal to all our processes
 * if daemonized  is ok to send the signal to all the process group,
 * however if not daemonized we might end up sending the signal also
 * to the shell which launched us => most signals will kill it if 
 * it's not in interactive mode and we don't want this. The non-daemonized 
 * case can occur when an error is encountered before daemonize is called 
 * (e.g. when parsing the config file) or when openser is started in 
 * "dont-fork" mode. Sending the signal to all the processes in pt[] will not 
 * work for processes forked from modules (which have no correspondent entry in
 * pt), but this can happen only in dont_fork mode (which is only for
 * debugging). So in the worst case + "dont-fork" we might leave some
 * zombies. -- andrei */
static void kill_all_children(int signum)
{
	int r;
	if (own_pgid) kill(0, signum);
	else if (pt)
		for (r=1; r<process_count(); r++)
			if (pt[r].pid) kill(pt[r].pid, signum);
}



/* if this handler is called, a critical timeout has occured while
 * waiting for the children to finish => we should kill everything and exit */
static void sig_alarm_kill(int signo)
{
	kill_all_children(SIGKILL); /* this will kill the whole group
								  including "this" process;
								  for debugging replace with SIGABRT
								  (but warning: it might generate lots
								   of cores) */
}


/* like sig_alarm_kill, but the timeout has occured when cleaning up
 * => try to leave a core for future diagnostics */
static void sig_alarm_abort(int signo)
{
	/* LOG is not signal safe, but who cares, we are abort-ing anyway :-) */
	LOG(L_CRIT, "BUG: shutdown timeout triggered, dying...");
	abort();
}


#define OPENSER_SHUTDOWN_TIME	60
void handle_sigs()
{
	pid_t	chld;
	int	chld_status;

	switch(sig_flag){
		case 0: break; /* do nothing*/
		case SIGPIPE:
				/* SIGPIPE might be rarely received on use of
				   exec module; simply ignore it
				 */
				LOG(L_WARN, "WARNING: SIGPIPE received and ignored\n");
				break;
		case SIGINT:
		case SIGTERM:
			/* we end the program in all these cases */
			if (sig_flag==SIGINT)
				DBG("INT received, program terminates\n");
			else
				DBG("SIGTERM received, program terminates\n");
				
			/* first of all, kill the children also */
			kill_all_children(SIGTERM);
			if (signal(SIGALRM, sig_alarm_kill) == SIG_ERR ) {
				LOG(L_ERR, "ERROR: could not install SIGALARM handler\n");
				/* continue, the process will die anyway if no
				 * alarm is installed which is exactly what we want */
			}
			alarm(OPENSER_SHUTDOWN_TIME); /* 1 minute close timeout */

			while(wait(0) > 0); /* Wait for all the children to terminate */
			signal(SIGALRM, sig_alarm_abort);

			cleanup(1); /* cleanup & show status*/
			alarm(0);
			signal(SIGALRM, SIG_IGN);
			dprint("Thank you for flying " NAME "\n");
			exit(0);
			break;
			
		case SIGUSR1:
#ifdef PKG_MALLOC
			LOG(memlog, "Memory status (pkg):\n");
			pkg_status();
#endif
#ifdef SHM_MEM
			LOG(memlog, "Memory status (shm):\n");
			shm_status();
#endif
			break;
			
		case SIGCHLD:
			while ((chld=waitpid( -1, &chld_status, WNOHANG ))>0) {
				if (WIFEXITED(chld_status)) 
					LOG(L_INFO, "child process %d exited normally,"
							" status=%d\n", chld, 
							WEXITSTATUS(chld_status));
				else if (WIFSIGNALED(chld_status)) {
					LOG(L_INFO, "child process %d exited by a signal"
							" %d\n", chld, WTERMSIG(chld_status));
#ifdef WCOREDUMP
					LOG(L_INFO, "core was %sgenerated\n",
							 WCOREDUMP(chld_status) ?  "" : "not " );
#endif
				}else if (WIFSTOPPED(chld_status)) 
					LOG(L_INFO, "child process %d stopped by a"
								" signal %d\n", chld,
								 WSTOPSIG(chld_status));
			}
			if (dont_fork) {
				LOG(L_INFO, "INFO: dont_fork turned on, living on\n");
				break;
			}
			LOG(L_INFO, "INFO: terminating due to SIGCHLD\n");
			/* exit */
			kill_all_children(SIGTERM);
			if (signal(SIGALRM, sig_alarm_kill) == SIG_ERR ) {
				LOG(L_ERR, "ERROR: could not install SIGALARM handler\n");
				/* continue, the process will die anyway if no
				 * alarm is installed which is exactly what we want */
			}
			alarm(OPENSER_SHUTDOWN_TIME); /* 1 minute close timeout */
			while(wait(0) > 0); /* wait for all the children to terminate*/
			signal(SIGALRM, sig_alarm_abort);
			cleanup(1); /* cleanup & show status*/
			alarm(0);
			signal(SIGALRM, SIG_IGN);
			DBG("terminating due to SIGCHLD\n");
			exit(0);
			break;
		
		case SIGHUP: /* ignoring it*/
					DBG("SIGHUP received, ignoring it\n");
					break;
		default:
			LOG(L_CRIT, "WARNING: unhandled signal %d\n", sig_flag);
	}
	sig_flag=0;
}



/* added by jku; allows for regular exit on a specific signal;
   good for profiling which only works if exited regularly and
   not by default signal handlers
    - modified by andrei: moved most of the stuff to handle_sigs, 
       made it safer for the "fork" case
*/
static void sig_usr(int signo)
{
	if (is_main){
		if (sig_flag==0) sig_flag=signo;
		else /*  previous sig. not processed yet, ignoring? */
			return; ;
		if (dont_fork) 
				/* only one proc, doing everything from the sig handler,
				unsafe, but this is only for debugging mode*/
			handle_sigs();
	}else{
		/* process the important signals */
		switch(signo){
			case SIGPIPE:
					LOG(L_INFO, "INFO: signal %d received\n", signo);
				break;
			case SIGINT:
			case SIGTERM:
					LOG(L_INFO, "INFO: signal %d received\n", signo);
					/* print memory stats for non-main too */
					#ifdef PKG_MALLOC
					LOG(memlog, "Memory status (pkg):\n");
					pkg_status();
					#endif
					exit(0);
					break;
			case SIGUSR1:
					/* statistics -> show only pkg mem */
					#ifdef PKG_MALLOC
					LOG(memlog, "Memory status (pkg):\n");
					pkg_status();
					#endif
					break;
				/* ignored*/
			case SIGUSR2:
			case SIGHUP:
					break;
			case SIGCHLD:
					DBG("SIGCHLD received: "
						"we do not worry about grand-children\n");
		}
	}
}



/* install the signal handlers, returns 0 on success, -1 on error */
int install_sigs()
{
	/* added by jku: add exit handler */
	if (signal(SIGINT, sig_usr) == SIG_ERR ) {
		DPrint("ERROR: no SIGINT signal handler can be installed\n");
		goto error;
	}
	/* if we debug and write to a pipe, we want to exit nicely too */
	if (signal(SIGPIPE, sig_usr) == SIG_ERR ) {
		DPrint("ERROR: no SIGINT signal handler can be installed\n");
		goto error;
	}
	
	if (signal(SIGUSR1, sig_usr)  == SIG_ERR ) {
		DPrint("ERROR: no SIGUSR1 signal handler can be installed\n");
		goto error;
	}
	if (signal(SIGCHLD , sig_usr)  == SIG_ERR ) {
		DPrint("ERROR: no SIGCHLD signal handler can be installed\n");
		goto error;
	}
	if (signal(SIGTERM , sig_usr)  == SIG_ERR ) {
		DPrint("ERROR: no SIGTERM signal handler can be installed\n");
		goto error;
	}
	if (signal(SIGHUP , sig_usr)  == SIG_ERR ) {
		DPrint("ERROR: no SIGHUP signal handler can be installed\n");
		goto error;
	}
	if (signal(SIGUSR2 , sig_usr)  == SIG_ERR ) {
		DPrint("ERROR: no SIGUSR2 signal handler can be installed\n");
		goto error;
	}
	return 0;
error:
	return -1;
}



/* main loop */
int main_loop()
{
	static int chd_rank;
	int  i;
	pid_t pid;
	struct socket_info* si;
#ifdef USE_TCP
	int sockfd[2];
#endif

	/* one "main" process and n children handling i/o */
	is_main=0;
	chd_rank=0;

	if (dont_fork){
		if (udp_listen==0){
			LOG(L_ERR, "ERROR: no fork mode requires at least one"
					" udp listen address, exiting...\n");
			goto error;
		}
		/* only one address, we ignore all the others */
		if (udp_init(udp_listen)==-1) goto error;
		bind_address=udp_listen;
		sendipv4=bind_address;
		sendipv6=bind_address; /*FIXME*/
		if (udp_listen->next){
			LOG(L_WARN, "WARNING: using only the first listen address"
						" (no fork)\n");
		}
		/* Initialize Unix domain socket server */
		if (init_unixsock_socket()<0) {
			LOG(L_ERR, "Error while creating unix domain sockets\n");
			goto error;
		}
		if (do_suid()==-1) goto error; /* try to drop privileges */
		/* process_no now initialized to zero -- increase from now on
		   as new processes are forked (while skipping 0 reserved for main 
		*/

		/* we need another process to act as the timer*/
#ifdef USE_TCP
		/* if we are using tcp we always need a timer process,
		 * we cannot count on select timeout to measure time
		 * (it works only on linux)
		 */
		if ((!tcp_disable)||(has_timers()))
#else
		if (has_timers())
#endif
		{
				process_no++;
				if ((pid=fork())<0){
					LOG(L_CRIT,  "ERROR: main_loop: Cannot fork\n");
					goto error;
				}
				
				if (pid==0){
					/* child */
					/* record pid twice to avoid the child using it, before
					 * parent gets a chance to set it*/
					pt[process_no].pid=getpid();
					/* timer!*/
					/* process_bit = 0; */
					if (init_child(PROC_TIMER) < 0) {
						LOG(L_ERR, "timer: init_child failed\n");
						goto error;
					}
					run_timer();
				}else{
						pt[process_no].pid=pid; /*should be shared mem anyway*/
						strncpy(pt[process_no].desc, "timer", MAX_PT_DESC );
				}
		}

		if (init_unixsock_children()<0) {
			LOG(L_ERR, "Error while initializing Unix domain socket server\n");
			goto error;
		}

		/* main process, receive loop */
		process_no=0; /*main process number*/
		pt[process_no].pid=getpid();
		snprintf(pt[process_no].desc, MAX_PT_DESC, 
			"stand-alone receiver @ %s:%s", 
			 bind_address->name.s, bind_address->port_no_str.s );

		/* We will call child_init even if we
		 * do not fork - and it will be called with rank 1 because
		 * in fact we behave like a child, not like main process */
		if (init_child(1) < 0) {
			LOG(L_ERR, "main_dontfork: init_child failed\n");
			goto error;
		}

		is_main=1; /* hack 42: call init_child with is_main=0 in case
					 some modules wants to fork a child */
		
		return udp_rcv_loop();
	}else{
		/* process_no now initialized to zero -- increase from now on
		   as new processes are forked (while skipping 0 reserved for main )
		*/

		for(si=udp_listen;si;si=si->next){
			/* create the listening socket (for each address)*/
			/* udp */
			if (udp_init(si)==-1) goto error;
			/* get first ipv4/ipv6 socket*/
			if ((si->address.af==AF_INET)&&
					((sendipv4==0)||(sendipv4->flags&SI_IS_LO)))
				sendipv4=si;
	#ifdef USE_IPV6
			if((sendipv6==0)&&(si->address.af==AF_INET6))
				sendipv6=si;
	#endif
		}
#ifdef USE_TCP
		if (!tcp_disable){
			for(si=tcp_listen; si; si=si->next){
				/* same thing for tcp */
				if (tcp_init(si)==-1)  goto error;
				/* get first ipv4/ipv6 socket*/
				if ((si->address.af==AF_INET)&&
						((sendipv4_tcp==0)||(sendipv4_tcp->flags&SI_IS_LO)))
					sendipv4_tcp=si;
		#ifdef USE_IPV6
				if((sendipv6_tcp==0)&&(si->address.af==AF_INET6))
					sendipv6_tcp=si;
		#endif
			}
		}
#ifdef USE_TLS
		if (!tls_disable){
			for(si=tls_listen; si; si=si->next){
				/* same as for tcp*/
				if (tls_init(si)==-1)  goto error;
				/* get first ipv4/ipv6 socket*/
				if ((si->address.af==AF_INET)&&
						((sendipv4_tls==0)||(sendipv4_tls->flags&SI_IS_LO)))
					sendipv4_tls=si;
		#ifdef USE_IPV6
				if((sendipv6_tls==0)&&(si->address.af==AF_INET6))
					sendipv6_tls=si;
		#endif
			}
		}
#endif /* USE_TLS */
#endif /* USE_TCP */

		/* Initialize Unix domain socket server */
		if (init_unixsock_socket()<0) {
			LOG(L_ERR, "ERROR: Could not create unix domain sockets\n");
			goto error;
		}

			/* all processes should have access to all the sockets (for sending)
			 * so we open all first*/
		if (do_suid()==-1) goto error; /* try to drop privileges */

		/* Spawn children listening on unix domain socket if and only if
		 * the unix domain socket server has not been disabled (i == 0) */
		if (init_unixsock_children()<0) {
			LOG(L_ERR, "ERROR: Could not initialize unix domain socket server\n");
			goto error;
		}

		/* udp processes */
		for(si=udp_listen; si; si=si->next){
			for(i=0;i<children_no;i++){
				process_no++;
				chd_rank++;
#ifdef USE_TCP
				if(!tcp_disable){
		 			if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd)<0){
						LOG(L_ERR, "ERROR: main_loop: socketpair failed: %s\n",
							strerror(errno));
						goto error;
					}
				}
#endif
				if ((pid=fork())<0){
					LOG(L_CRIT,  "main_loop: Cannot fork\n");
					goto error;
				}else if (pid==0){
					     /* child */
#ifdef USE_TCP
					if (!tcp_disable){
						close(sockfd[0]);
						unix_tcp_sock=sockfd[1];
					}
#endif
					/* record pid twice to avoid the child using it, before
					 * parent gets a chance to set it*/
					pt[process_no].pid=getpid();
					bind_address=si; /* shortcut */
					if (init_child(chd_rank) < 0) {
						LOG(L_ERR, "init_child failed\n");
						goto error;
					}
					return udp_rcv_loop();
				}else{
						pt[process_no].pid=pid; /*should be in shared mem.*/
						snprintf(pt[process_no].desc, MAX_PT_DESC,
							"receiver child=%d sock= %s:%s", i, 	
							si->name.s, si->port_no_str.s );
#ifdef USE_TCP
						if (!tcp_disable){
							close(sockfd[1]);
							pt[process_no].unix_sock=sockfd[0];
							pt[process_no].idx=-1; /* this is not a "tcp"
													  process*/
						}
#endif
				}
			}
			/*parent*/
			/*close(udp_sock)*/; /*if it's closed=>sendto invalid fd errors?*/
		}
	}

	/* this is the main process -> it shouldn't send anything */
	bind_address=0;

#ifdef USE_TCP
	/* if we are using tcp we always need the timer */
	if ((!tcp_disable)||(has_timers()))
#else
	if (has_timers())
#endif
	{
#ifdef USE_TCP
		if (!tcp_disable){
 			if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfd)<0){
				LOG(L_ERR, "ERROR: main_loop: socketpair failed: %s\n",
					strerror(errno));
				goto error;
			}
		}
#endif
		/* fork again for the timer process*/
		process_no++;
		if ((pid=fork())<0){
			LOG(L_CRIT, "main_loop: cannot fork timer process\n");
			goto error;
		}else if (pid==0){
			/* child */
			/* is_main=0; */
#ifdef USE_TCP
			if (!tcp_disable){
				close(sockfd[0]);
				unix_tcp_sock=sockfd[1];
			}
#endif
			/* record pid twice to avoid the child using it, before
			 * parent gets a chance to set it*/
			pt[process_no].pid=getpid();
			if (init_child(PROC_TIMER) < 0) {
				LOG(L_ERR, "timer: init_child failed\n");
				goto error;
			}
			
			run_timer();
		}else{
			pt[process_no].pid=pid;
			strncpy(pt[process_no].desc, "timer", MAX_PT_DESC );
#ifdef USE_TCP
			if(!tcp_disable){
						close(sockfd[1]);
						pt[process_no].unix_sock=sockfd[0];
						pt[process_no].idx=-1; /* this is not a "tcp" process*/
			}
#endif
		}
	}
#ifdef USE_TCP
		if (!tcp_disable){
				/* start tcp  & tls receivers */
			if (tcp_init_children(&chd_rank)<0) goto error;
				/* start tcp+tls master proc */
			process_no++;
			if ((pid=fork())<0){
				LOG(L_CRIT, "main_loop: cannot fork tcp main process\n");
				goto error;
			}else if (pid==0){
				/* child */
				/* is_main=0; */
				/* record pid twice to avoid the child using it, before
				 * parent gets a chance to set it*/
				pt[process_no].pid=getpid();
				if (init_child(PROC_TCP_MAIN) < 0) {
					LOG(L_ERR, "tcp_main: error in init_child\n");
					goto error;
				}
				tcp_main_loop();
			}else{
				pt[process_no].pid=pid;
				strncpy(pt[process_no].desc, "tcp main process", MAX_PT_DESC );
				pt[process_no].unix_sock=-1;
				pt[process_no].idx=-1; /* this is not a "tcp" process*/
				unix_tcp_sock=-1;
			}
		}
#endif
	/* main */
	pt[0].pid=getpid();
	strncpy(pt[0].desc, "attendant", MAX_PT_DESC );
#ifdef USE_TCP
	if(!tcp_disable){
		pt[process_no].unix_sock=-1;
		pt[process_no].idx=-1; /* this is not a "tcp" process*/
		unix_tcp_sock=-1;
	}
#endif
	process_no=0; 
	/* process_bit = 0; */
	is_main=1;
	
	if (init_child(PROC_MAIN) < 0) {
		LOG(L_ERR, "main: error in init_child\n");
		goto error;
	}
	for(;;){
			pause();
			handle_sigs();
	}
	
	
	/*return 0; */
 error:
	is_main=1;  /* if we are here, we are the "main process",
				  any forked children should exit with exit(-1) and not
				  ever use return */
	return -1;

}




int main(int argc, char** argv)
{

	FILE* cfg_stream;
	int c,r;
	char *tmp;
	int tmp_len;
	int port;
	int proto;
	char *options;
	int ret;
	unsigned int seed;
	int rfd;

	/*init*/
	ret=-1;
	my_argc=argc; my_argv=argv;
	
	/*init pkg mallocs (before parsing cfg or cmd line !)*/
	if (init_pkg_mallocs()==-1)
		goto error;

	init_route_lists();
	/* process command line (get port no, cfg. file path etc) */
	opterr=0;
	options="f:cCm:b:l:n:N:rRvdDETVhw:t:u:g:P:G:i:x:W:";
	
	while((c=getopt(argc,argv,options))!=-1){
		switch(c){
			case 'f':
					cfg_file=optarg;
					break;
			case 'C':
					config_check |= 2;
			case 'c':
					if (config_check==3)
						break;
					config_check |= 1;
					log_stderr=1; /* force stderr logging */
					break;
			case 'm':
					shm_mem_size=strtol(optarg, &tmp, 10) * 1024 * 1024;
					if (tmp &&(*tmp)){
						fprintf(stderr, "bad shmem size number: -m %s\n",
										optarg);
						goto error;
					};
					LOG(L_INFO, "openser:main: shared memory: %ld bytes\n",
									shm_mem_size );
					break;

			case 'b':
					maxbuffer=strtol(optarg, &tmp, 10);
					if (tmp &&(*tmp)){
						fprintf(stderr, "bad max buffer size number: -p %s\n",
											optarg);
						goto error;
					}
					break;
			case 'l':
					if (parse_phostport(optarg, strlen(optarg), &tmp, &tmp_len,
											&port, &proto)<0){
						fprintf(stderr, "bad -l address specifier: %s\n",
										optarg);
						goto error;
					}
					tmp[tmp_len]=0; /* null terminate the host */
					/* add a new addr. to our address list */
					if (add_listen_iface(tmp, port, proto, 0)!=0){
						fprintf(stderr, "failed to add new listen address\n");
						goto error;
					}
					break;
			case 'n':
					children_no=strtol(optarg, &tmp, 10);
					if ((tmp==0) ||(*tmp)){
						fprintf(stderr, "bad process number: -n %s\n",
									optarg);
						goto error;
					}
					break;
			case 'v':
					check_via=1;
					break;
			case 'r':
					received_dns|=DO_DNS;
					break;
			case 'R':
					received_dns|=DO_REV_DNS;
			case 'd':
					debug++;
					break;
			case 'D':
					dont_fork=1;
					break;
			case 'E':
					log_stderr=1;
					break;
			case 'T':
#ifdef USE_TCP
					tcp_disable=1;
#else
					fprintf(stderr,"WARNING: tcp support not compiled in\n");
#endif
					break;
			case 'N':
#ifdef USE_TCP
					tcp_children_no=strtol(optarg, &tmp, 10);
					if ((tmp==0) ||(*tmp)){
						fprintf(stderr, "bad process number: -N %s\n",
									optarg);
						goto error;
					}
#else
					fprintf(stderr,"WARNING: tcp support not compiled in\n");
#endif
					break;
			case 'W':
#ifdef USE_TCP
					tcp_poll_method=get_poll_type(optarg);
					if (tcp_poll_method==POLL_NONE){
						fprintf(stderr, "bad poll method name: -W %s\ntry "
								"one of %s.\n", optarg, poll_support);
						goto error;
					}
#else
					fprintf(stderr,"WARNING: tcp support not compiled in\n");
#endif
					break;
			case 'V':
					printf("version: %s\n", version);
					printf("flags: %s\n", flags );
					print_ct_constants();
					printf("%s\n",id);
					printf("%s compiled on %s with %s\n", __FILE__,
							compiled, COMPILER );
					
					exit(0);
					break;
			case 'h':
					printf("version: %s\n", version);
					printf("%s",help_msg);
					exit(0);
					break;
			case 'w':
					working_dir=optarg;
					break;
			case 't':
					chroot_dir=optarg;
					break;
			case 'u':
					user=optarg;
					break;
			case 'g':
					group=optarg;
					break;
			case 'P':
					pid_file=optarg;
					break;
			case 'G':
					pgid_file=optarg;
					break;
			case 'x':
					unixsock_name=optarg;
					break;
			case '?':
					if (isprint(optopt))
						fprintf(stderr, "Unknown option `-%c´.\n", optopt);
					else
						fprintf(stderr, 
								"Unknown option character `\\x%x´.\n",
								optopt);
					goto error;
			case ':':
					fprintf(stderr, 
								"Option `-%c´ requires an argument.\n",
								optopt);
					goto error;
			default:
					abort();
		}
	}
	
	/* fill missing arguments with the default values*/
	if (cfg_file==0) cfg_file=CFG_FILE;

	/* load config file or die */
	cfg_stream=fopen (cfg_file, "r");
	if (cfg_stream==0){
		fprintf(stderr, "ERROR: loading config file(%s): %s\n", cfg_file,
				strerror(errno));
		goto error;
	}

	/* seed the prng */
	/* try to use /dev/urandom if possible */
	seed=0;
	if ((rfd=open("/dev/urandom", O_RDONLY))!=-1){
try_again:
		if (read(rfd, (void*)&seed, sizeof(seed))==-1){
			if (errno==EINTR) goto try_again; /* interrupted by signal */
			LOG(L_WARN, "WARNING: could not read from /dev/urandom (%d)\n",
						errno);
		}
		DBG("read %u from /dev/urandom\n", seed);
			close(rfd);
	}else{
		LOG(L_WARN, "WARNING: could not open /dev/urandom (%d)\n", errno);
	}
	seed+=getpid()+time(0);
	DBG("seeding PRNG with %u\n", seed);
	srand(seed);
	DBG("test random number %u\n", rand());

	/*register builtin  modules*/
	register_builtin_modules();

#ifdef USE_TLS
	/* initialize default TLS domains,
	   must be done before reading the config */
	if (pre_init_tls()<0){
		LOG(L_CRIT, "ERROR:main:could not pre_init_tls, exiting...\n");
		goto error;
	}
#endif /* USE_TLS */

	if (preinit_black_lists()!=0) {
		LOG(L_CRIT, "ERROR:main: failed to alloc black list's anchor\n");
		goto error;
	}

	yyin=cfg_stream;
	if ((yyparse()!=0)||(cfg_errors)){
		fprintf(stderr, "ERROR: bad config file (%d errors)\n", cfg_errors);
		goto error;
	}

	if (config_check>1 && check_rls()!=0) {
		fprintf(stderr, "ERROR: bad function call in config file\n");
		goto error;
	}
	print_rl();

	/* init the resolver, before fixing the config */
	resolv_init();

	/* fix parameters */
	if (port_no<=0) port_no=SIP_PORT;
#ifdef USE_TLS
	if (tls_port_no<=0) tls_port_no=SIPS_PORT;
#endif
	
	
	if (children_no<=0) children_no=CHILD_NO;
#ifdef USE_TCP
	if (!tcp_disable){
		if (tcp_children_no<=0) tcp_children_no=children_no;
	}
#endif
	
	if (working_dir==0) working_dir="/";

	/* get uid/gid */
	if (user){
		if (user2uid(&uid, &gid, user)<0){
			fprintf(stderr, "bad user name/uid number: -u %s\n", user);
			goto error;
		}
	}
	if (group){
		if (group2gid(&gid, group)<0){
				fprintf(stderr, "bad group name/gid number: -u %s\n", group);
			goto error;
		}
	}
	/* fix sock uid/gid */
	if (sock_user){
		if (user2uid(&sock_uid, 0, sock_user)<0){
			fprintf(stderr, "bad socket user name/uid number %s\n", user);
			goto error;
		}
	}
	if (sock_group){
		if (group2gid(&sock_gid, sock_group)<0){
			fprintf(stderr, "bad group name/gid number: -u %s\n", group);
			goto error;
		}
	}
	if (fix_all_socket_lists()!=0){
		fprintf(stderr,  "failed to initialize list addresses\n");
		goto error;
	}
	/* print all the listen addresses */
	printf("Listening on \n");
	print_all_socket_lists();
	printf("Aliases: \n");
	/*print_aliases();*/
	print_aliases();
	printf("\n");
	
	if (dont_fork){
		fprintf(stderr, "WARNING: no fork mode %s\n", 
				(udp_listen)?(
				(udp_listen->next)?" and more than one listen address found"
				"(will use only the the first one)":""
				):"and no udp listen address found" );
	}
	if (config_check){
		fprintf(stderr, "config file ok, exiting...\n");
		ret = 0;
		goto error;
	}


	/*init shm mallocs
	 *  this must be here 
	 *     -to allow setting shm mem size from the command line
	 *       => if shm_mem should be settable from the cfg file move
	 *       everything after
	 *     -it must be also before init_timer and init_tcp
	 *     -it must be after we know uid (so that in the SYSV sems case,
	 *        the sems will have the correct euid)
	 * --andrei */
	if (init_shm_mallocs()==-1)
		goto error;
	/*init timer, before parsing the cfg!*/
	if (init_timer()<0){
		LOG(L_CRIT, "could not initialize timer, exiting...\n");
		goto error;
	}
	
#ifdef USE_TCP
	if (!tcp_disable){
		/*init tcp*/
		if (init_tcp()<0){
			LOG(L_CRIT, "could not initialize tcp, exiting...\n");
			goto error;
		}
	}
#ifdef USE_TLS
	if (!tls_disable){
		/* init tls*/
		if (init_tls()<0){
			LOG(L_CRIT, "could not initialize tls, exiting...\n");
			goto error;
		}
	}
#endif /* USE_TLS */
#endif /* USE_TCP */
	/* init_daemon? */
	if (!dont_fork){
		if ( daemonize((log_name==0)?argv[0]:log_name) <0 ) goto error;
	}
	if (install_sigs() != 0){
		fprintf(stderr, "ERROR: could not install the signal handlers\n");
		goto error;
	}
	
	
	/*alloc pids*/
#ifdef SHM_MEM
	pt=shm_malloc(sizeof(struct process_table)*process_count());
#else
	pt=pkg_malloc(sizeof(struct process_table)*process_count());
#endif
	if (pt==0){
		fprintf(stderr, "ERROR: out  of memory\n");
		goto error;
	}
	memset(pt, 0, sizeof(struct process_table)*process_count());

	if (disable_core_dump) set_core_dump(0, 0);
	else set_core_dump(1, shm_mem_size+PKG_MEM_POOL_SIZE+4*1024*1024);
	if (open_files_limit>0){
		if(increase_open_fds(open_files_limit)<0){ 
			fprintf(stderr, "ERROR: error could not increase file limits\n");
			goto error;
		}
	}

	/* init serial forking engine */
	if (init_serialization()!=0) {
		fprintf(stderr, "ERROR: error while initializing serialization\n");
		goto error;
	}
	/* Init statistics */
	if (init_stats_collector()<0) {
		LOG(L_ERR, "Error while initializing statistics\n");
		goto error;
	}
	/* Init MI */
	if (init_mi_core()<0) {
		LOG(L_ERR, "Error while initializing MI core\n");
		goto error;
	}
	/* init black list engine */
	if (init_black_lists()!=0) {
		LOG(L_CRIT, "ERROR:main: failed to init black lists\n");
		goto error;
	}
	/* init resolver's blacklist */
	if (resolv_blacklist_init()!=0) {
		LOG(L_CRIT, "ERROR:main: failed to create DNS blacklist\n");
		goto error;
	}

	/* init modules */
	if (init_modules() != 0) {
		fprintf(stderr, "ERROR: error while initializing modules\n");
		goto error;
	}
	/* fix routing lists */
	if ( (r=fix_rls())!=0){
		fprintf(stderr, "ERROR: error %d while trying to fix configuration\n",
						r);
		goto error;
	};


	ret=main_loop();

error:
	/*kill everything*/
	kill_all_children(SIGTERM);
	/*clean-up*/
	cleanup(0);
	return ret;
}



syntax highlighted by Code2HTML, v. 0.9.1