Skip to content
Snippets Groups Projects
sbbscon.c 38.38 KiB
/* sbbscon.c */

/* Synchronet vanilla/console-mode "front-end" */

/* $Id$ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright 2000 Rob Swindell - http://www.synchro.net/copyright.html		*
 *																			*
 * 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.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

/* ANSI headers */
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <ctype.h>
#ifdef __QNX__
#include <locale.h>
#endif

/* Synchronet-specific headers */
#include "conwrap.h"	/* kbhit/getch */
#include "sbbs.h"		/* load_cfg() */
#include "sbbs_ini.h"	/* sbbs_read_ini() */
#include "ftpsrvr.h"	/* ftp_startup_t, ftp_server */
#include "mailsrvr.h"	/* mail_startup_t, mail_server */
#include "services.h"	/* services_startup_t, services_thread */

#ifdef __unix__

#include <sys/types.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <stdarg.h>
#include <stdlib.h>  /* Is this included from somewhere else? */
#include <syslog.h>

#endif

/* Do not include web server in 3.10-Win32 release build */
#if !defined(_MSC_VER)
	#define WEB_SERVER
#endif

/* Constants */
#define SBBS_PID_FILE	"/var/run/sbbs.pid"
#define SBBS_LOG_NAME	"synchronet"

/* Global variables */
BOOL				run_bbs=TRUE;
BOOL				bbs_running=FALSE;
BOOL				bbs_stopped=FALSE;
bbs_startup_t		bbs_startup;
BOOL				run_ftp=TRUE;
BOOL				ftp_running=FALSE;
BOOL				ftp_stopped=FALSE;
ftp_startup_t		ftp_startup;
BOOL				run_mail=TRUE;
BOOL				mail_running=FALSE;
BOOL				mail_stopped=FALSE;
mail_startup_t		mail_startup;
BOOL				run_services=TRUE;
BOOL				services_running=FALSE;
BOOL				services_stopped=FALSE;
services_startup_t	services_startup;
BOOL				run_web=TRUE;
BOOL				web_running=FALSE;
BOOL				web_stopped=FALSE;
web_startup_t		web_startup;
uint				thread_count=1;
uint				socket_count=0;
uint				client_count=0;
int					prompt_len=0;
static scfg_t		scfg;					/* To allow rerun */
static ulong		served=0;

#ifdef __unix__
char				new_uid_name[32];
char				new_gid_name[32];
uid_t				new_uid;
uid_t				old_uid;
gid_t				new_gid;
gid_t				old_gid;
BOOL				is_daemon=FALSE;
BOOL				std_facilities=FALSE;
#endif

static const char* prompt;

static const char* usage  = "\nusage: %s [[setting] [...]] [path/ini_file]\n"
							"\n"
							"Telnet server settings:\n\n"
							"\ttf<node>   set first Telnet node number\n"
							"\ttl<node>   set last Telnet node number\n"
							"\ttp<port>   set Telnet server port\n"
							"\trp<port>   set RLogin server port (and enable RLogin server)\n"
							"\tr2         use second RLogin name in BSD RLogin\n"
							"\tto<value>  set Telnet server options value (advanced)\n"
							"\tta         enable auto-logon via IP address\n"
							"\ttd         enable Telnet command debug output\n"
							"\ttc         emabble sysop availability for chat\n"
							"\ttq         disable QWK events\n"
							"\tt-         disable Telnet/RLogin server\n"
							"\n"
							"FTP server settings:\n"
							"\n"
							"\tfp<port>   set FTP server port\n"
							"\tfo<value>  set FTP server options value (advanced)\n"
							"\tf-         disable FTP server\n"
							"\n"
							"Mail server settings:\n"
							"\n"
							"\tms<port>   set SMTP server port\n"
							"\tmp<port>   set POP3 server port\n"
							"\tmr<addr>   set SMTP relay server (and enable SMTP relay)\n"
							"\tmd<addr>   set DNS server address for MX-record lookups\n"
							"\tmo<value>  set Mail server options value (advanced)\n"
							"\tma         allow SMTP relays from authenticated users\n"
							"\tm-         disable Mail server (entirely)\n"
							"\tmp-        disable POP3 server\n"
							"\tms-        disable SendMail thread\n"
							"\n"
							"Services settings:\n"
							"\n"
							"\tso<value>  set Services option value (advanced)\n"
							"\ts-         disable Services (no services module)\n"
							"\n"
							"Global settings:\n"
							"\n"
							"\thn[host]   set hostname for this instance\n"
							"\t           if host not specified, uses gethostname\n"
#ifdef __unix__
							"\tun<user>   set username for BBS to run as\n"
							"\tug<group>  set group for BBS to run as\n"
							"\td[x]       run as daemon, log using syslog\n"
							"\t           x is the optional LOCALx facility to use\n"
							"\t           if none is specified, uses USER\n"
							"\t           if 'S' is specified, uses standard facilities\n"
#endif
							"\tgi         get user identity (using IDENT protocol)\n"
							"\tnh         disable hostname lookups\n"
							"\tnj         disable JavaScript support\n"
							"\tne         disable event thread\n"
							"\tni         do not read settings from .ini file\n"
#ifdef __unix__
							"\tnd         do not read run as daemon - overrides .ini file\n"
#endif
							"\tlt         use local timezone (do not force UTC/GMT)\n"
							"\tdefaults   show default settings and options\n"
							;

static int lputs(char *str)
{
	static pthread_mutex_t mutex;
	static BOOL mutex_initialized;

#ifdef __unix__

	if (is_daemon)  {
		if(str!=NULL) {
			if (std_facilities)
				syslog(LOG_INFO|LOG_AUTH,"%s",str);
			else
				syslog(LOG_INFO,"%s",str);
		}
		return(0);
	}
#endif
	if(!mutex_initialized) {
		pthread_mutex_init(&mutex,NULL);
		mutex_initialized=TRUE;
	}
	pthread_mutex_lock(&mutex);
	/* erase prompt */
	printf("\r%*s\r",prompt_len,"");
	if(str!=NULL)
		printf("%s\n",str);
	/* re-display prompt with current stats */
	if(prompt!=NULL)
		prompt_len = printf(prompt, thread_count, socket_count, client_count, served);
	fflush(stdout);
	pthread_mutex_unlock(&mutex);
    return(prompt_len);
}

#ifdef __unix__
/**********************************************************
* Change uid of the calling process to the user if specified
* **********************************************************/
static BOOL do_setuid(void) 
{
	BOOL	result=FALSE;

	setregid(-1,old_gid);
	setreuid(-1,old_uid);
	if(!setregid(new_gid,new_gid) && !setreuid(new_uid,new_uid)) 
		result=TRUE;

	if(!result) {
		lputs("!setuid FAILED");
		lputs(strerror(errno));
	}
	return result;
}

/**********************************************************
* Change uid of the calling process to the user if specified
* **********************************************************/
static BOOL do_seteuid(BOOL to_new) 
{
	BOOL	result=FALSE;
	static pthread_mutex_t mutex;
	static BOOL mutex_initialized;

	if(new_uid_name[0]==0)	/* not set? */
		return(TRUE);		/* do nothing */

	if(!mutex_initialized) {
		pthread_mutex_init(&mutex,NULL);
		mutex_initialized=TRUE;
	}

	pthread_mutex_lock(&mutex);

	if(to_new)
		if(!setregid(-1,new_gid) && !setreuid(-1,new_uid))
			result=TRUE;
		else
			result=FALSE;
	else
		if(!setregid(-1,old_gid) && !setreuid(-1,old_uid))
			result=TRUE;
		else
			result=FALSE;

		
	pthread_mutex_unlock(&mutex);

	if(!result) {
		lputs("!seteuid FAILED");
		lputs(strerror(errno));
	}
	return result;
}
#endif   /* __unix__ */

#ifdef _WINSOCKAPI_

static WSADATA WSAData;

static BOOL winsock_startup(void)
{
	int		status;             /* Status Code */

    if((status = WSAStartup(MAKEWORD(1,1), &WSAData))==0)
		return(TRUE);

    fprintf(stderr,"!WinSock startup ERROR %d\n", status);
	return(FALSE);
}

static BOOL winsock_cleanup(void)	
{
	if(WSACleanup()==0)
		return(TRUE);

	fprintf(stderr,"!WinSock cleanup ERROR %d\n",ERROR_VALUE);
	return(FALSE);
}

#else /* No WINSOCK */

#define winsock_startup()	(TRUE)	
#define winsock_cleanup()	(TRUE)

#endif

static void thread_up(BOOL up, BOOL setuid)
{
   	static pthread_mutex_t mutex;
	static BOOL mutex_initialized;

#ifdef _THREAD_SUID_BROKEN
	if(up && setuid) {
		do_seteuid(FALSE);
		do_setuid();
	}
#endif

	if(!mutex_initialized) {
		pthread_mutex_init(&mutex,NULL);
		mutex_initialized=TRUE;
	}

	pthread_mutex_lock(&mutex);
	if(up)
	    thread_count++;
    else if(thread_count>0)
    	thread_count--;
	pthread_mutex_unlock(&mutex);
	lputs(NULL); /* update displayed stats */
}

static void socket_open(BOOL open)
{
   	static pthread_mutex_t mutex;
	static BOOL mutex_initialized;

	if(!mutex_initialized) {
		pthread_mutex_init(&mutex,NULL);
		mutex_initialized=TRUE;
	}

	pthread_mutex_lock(&mutex);
	if(open)
	    socket_count++;
    else if(socket_count>0)
    	socket_count--;
	pthread_mutex_unlock(&mutex);
	lputs(NULL); /* update displayed stats */
}
static void client_on(BOOL on, int sock, client_t* client, BOOL update)
{
   	static pthread_mutex_t mutex;
	static BOOL mutex_initialized;

	if(!mutex_initialized) {
		pthread_mutex_init(&mutex,NULL);
		mutex_initialized=TRUE;
	}

	pthread_mutex_lock(&mutex);
	if(on && !update) {
		client_count++;
	    served++;
	} else if(!on && client_count>0)
		client_count--;
	pthread_mutex_unlock(&mutex);
	lputs(NULL); /* update displayed stats */
}

/****************************************************************************/
/* BBS local/log print routine												*/
/****************************************************************************/
static int bbs_lputs(char *str)
{
	char		logline[512];
	char		tstr[64];
	time_t		t;
	struct tm	tm;

#ifdef __unix__
	if (is_daemon)  {
		if(str==NULL)
			return(0);
		if (std_facilities)
			syslog(LOG_INFO|LOG_AUTH,"%s",str);
		else
			syslog(LOG_INFO,"     %s",str);
		return(strlen(str));
	}
#endif

	t=time(NULL);
	if(localtime_r(&t,&tm)==NULL)
		tstr[0]=0;
	else
		sprintf(tstr,"%d/%d %02d:%02d:%02d "
			,tm.tm_mon+1,tm.tm_mday
			,tm.tm_hour,tm.tm_min,tm.tm_sec);

	sprintf(logline,"%s     %.*s",tstr,(int)sizeof(logline)-32,str);
	truncsp(logline);
	lputs(logline);
	
    return(strlen(logline)+1);
}

static void bbs_started(void)
{
	bbs_running=TRUE;
	bbs_stopped=FALSE;
	#ifdef _THREAD_SUID_BROKEN
	    do_seteuid(FALSE);
	    do_setuid();
	#endif
}

static void bbs_terminated(int code)
{
	bbs_running=FALSE;
	bbs_stopped=TRUE;
}

/****************************************************************************/
/* FTP local/log print routine												*/
/****************************************************************************/
static int ftp_lputs(char *str)
{
	char		logline[512];
	char		tstr[64];
	time_t		t;
	struct tm	tm;

#ifdef __unix__
	if (is_daemon)  {
		if(str==NULL)
			return(0);
		if (std_facilities)
#ifdef __solaris__
			syslog(LOG_INFO|LOG_DAEMON,"%s",str);
#else
			syslog(LOG_INFO|LOG_FTP,"%s",str);
#endif
		else
			syslog(LOG_INFO,"ftp  %s",str);
		return(strlen(str));
	}
#endif

	t=time(NULL);
	if(localtime_r(&t,&tm)==NULL)
		tstr[0]=0;
	else
		sprintf(tstr,"%d/%d %02d:%02d:%02d "
			,tm.tm_mon+1,tm.tm_mday
			,tm.tm_hour,tm.tm_min,tm.tm_sec);

	sprintf(logline,"%sftp  %.*s",tstr,(int)sizeof(logline)-32,str);
	truncsp(logline);
	lputs(logline);
	
    return(strlen(logline)+1);
}

static void ftp_started(void)
{
	ftp_running=TRUE;
	ftp_stopped=FALSE;
	#ifdef _THREAD_SUID_BROKEN
	    do_seteuid(FALSE);
	    do_setuid();
	#endif
}

static void ftp_terminated(int code)
{
	ftp_running=FALSE;
	ftp_stopped=TRUE;
}

/****************************************************************************/
/* Mail Server local/log print routine										*/
/****************************************************************************/
static int mail_lputs(char *str)
{
	char		logline[512];
	char		tstr[64];
	time_t		t;
	struct tm	tm;
#ifdef __unix__
	if (is_daemon)  {
		if(str==NULL)
			return(0);
		if (std_facilities)
			syslog(LOG_INFO|LOG_MAIL,"%s",str);
		else
			syslog(LOG_INFO,"mail %s",str);
		return(strlen(str));
	}
#endif

	t=time(NULL);
	if(localtime_r(&t,&tm)==NULL)
		tstr[0]=0;
	else
		sprintf(tstr,"%d/%d %02d:%02d:%02d "
			,tm.tm_mon+1,tm.tm_mday
			,tm.tm_hour,tm.tm_min,tm.tm_sec);

	sprintf(logline,"%smail %.*s",tstr,(int)sizeof(logline)-32,str);
	truncsp(logline);
	lputs(logline);
	
    return(strlen(logline)+1);
}

static void mail_started(void)
{
	mail_running=TRUE;
	mail_stopped=FALSE;
	#ifdef _THREAD_SUID_BROKEN
	    do_seteuid(FALSE);
	    do_setuid();
	#endif
}

static void mail_terminated(int code)
{
	mail_running=FALSE;
	mail_stopped=TRUE;
}

/****************************************************************************/
/* Services local/log print routine											*/
/****************************************************************************/
static int services_lputs(char *str)
{
	char		logline[512];
	char		tstr[64];
	time_t		t;
	struct tm	tm;

#ifdef __unix__
	if (is_daemon)  {
		if(str==NULL)
			return(0);
		if (std_facilities)
			syslog(LOG_INFO|LOG_DAEMON,"%s",str);
		else
			syslog(LOG_INFO,"srvc %s",str);
		return(strlen(str));
	}
#endif

	t=time(NULL);
	if(localtime_r(&t,&tm)==NULL)
		tstr[0]=0;
	else
		sprintf(tstr,"%d/%d %02d:%02d:%02d "
			,tm.tm_mon+1,tm.tm_mday
			,tm.tm_hour,tm.tm_min,tm.tm_sec);

	sprintf(logline,"%ssrvc %.*s",tstr,(int)sizeof(logline)-32,str);
	truncsp(logline);
	lputs(logline);
	
    return(strlen(logline)+1);
}

static void services_started(void)
{
	services_running=TRUE;
	services_stopped=FALSE;
	#ifdef _THREAD_SUID_BROKEN
	    do_seteuid(FALSE);
	    do_setuid();
	#endif
}

static void services_terminated(int code)
{
	services_running=FALSE;
	services_stopped=TRUE;
}

/****************************************************************************/
/* Event thread local/log print routine										*/
/****************************************************************************/
static int event_lputs(char *str)
{
	char		logline[512];
	char		tstr[64];
	time_t		t;
	struct tm	tm;

#ifdef __unix__
	if (is_daemon)  {
		if(str==NULL)
			return(0);
		if (std_facilities)
			syslog(LOG_INFO|LOG_CRON,"%s",str);
		else
			syslog(LOG_INFO,"evnt %s",str);
		return(strlen(str));
	}
#endif

	t=time(NULL);
	if(localtime_r(&t,&tm)==NULL)
		tstr[0]=0;
	else
		sprintf(tstr,"%d/%d %02d:%02d:%02d "
			,tm.tm_mon+1,tm.tm_mday
			,tm.tm_hour,tm.tm_min,tm.tm_sec);

	sprintf(logline,"%sevnt %.*s",tstr,(int)sizeof(logline)-32,str);
	truncsp(logline);
	lputs(logline);
	
    return(strlen(logline)+1);
}

/****************************************************************************/
/* web local/log print routine											*/
/****************************************************************************/
static int web_lputs(char *str)
{
	char		logline[512];
	char		tstr[64];
	time_t		t;
	struct tm	tm;

#ifdef __unix__
	if (is_daemon)  {
		if(str==NULL)
			return(0);
		if (std_facilities)
			syslog(LOG_INFO|LOG_DAEMON,"%s",str);
		else
			syslog(LOG_INFO,"web  %s",str);
		return(strlen(str));
	}
#endif

	t=time(NULL);
	if(localtime_r(&t,&tm)==NULL)
		tstr[0]=0;
	else
		sprintf(tstr,"%d/%d %02d:%02d:%02d "
			,tm.tm_mon+1,tm.tm_mday
			,tm.tm_hour,tm.tm_min,tm.tm_sec);

	sprintf(logline,"%sweb  %.*s",tstr,(int)sizeof(logline)-32,str);
	truncsp(logline);
	lputs(logline);
	
    return(strlen(logline)+1);
}

static void web_started(void)
{
	web_running=TRUE;
	web_stopped=FALSE;
	#ifdef _THREAD_SUID_BROKEN
	    do_seteuid(FALSE);
	    do_setuid();
	#endif
}

static void web_terminated(int code)
{
	web_running=FALSE;
	web_stopped=TRUE;
}

static void terminate(void)
{
	ulong count=0;

	bbs_terminate();
	ftp_terminate();
#ifdef WEB_SERVER
	web_terminate();
#endif
	mail_terminate();
#ifdef JAVASCRIPT
	services_terminate();
#endif

	while(bbs_running || ftp_running || web_running || mail_running || services_running)  {
		if(count && (count%10)==0) {
			if(bbs_running)
				bbs_lputs("BBS System thread still running");
			if(ftp_running)
				ftp_lputs("FTP Server thread still running");
			if(web_running)
				web_lputs("Web Server thread still running");
			if(mail_running)
				mail_lputs("Mail Server thread still running");
			if(services_running)
				services_lputs("Services thread still running");
		}
		count++;
		SLEEP(1000);
	}
}

#ifdef __unix__
void _sighandler_quit(int sig)
{
	char	str[1024];
	static pthread_mutex_t mutex;
	static BOOL mutex_initialized;

	if(!mutex_initialized) {
		pthread_mutex_init(&mutex,NULL);
		mutex_initialized=TRUE;
	}
	pthread_mutex_lock(&mutex);
	/* Can I get away with leaving this locked till exit? */

	sprintf(str,"     Got quit signal (%d)",sig);
	lputs(str);
	terminate();

	if(is_daemon)
		unlink(SBBS_PID_FILE);

    exit(0);
}

void _sighandler_rerun(int sig)
{
	int		i;
	node_t	node;

	lputs("     Got HUP (rerun) signal");
	
	for(i=1;i<=scfg.sys_nodes;i++) {
		getnodedat(&scfg,i,&node,NULL /* file */);
		node.misc|=NODE_RRUN;
		printnodedat(&scfg,i,&node);
	}
}

#ifdef NEEDS_DAEMON
/****************************************************************************/
/* Daemonizes the process                                                   */
/****************************************************************************/
int
daemon(nochdir, noclose)
    int nochdir, noclose;
{
    int fd;

    switch (fork()) {
    case -1:
        return (-1);
    case 0:
        break;
    default:
        _exit(0);
    }

    if (setsid() == -1)
        return (-1);

    if (!nochdir)
        (void)chdir("/");

    if (!noclose && (fd = open("/dev/null", O_RDWR, 0)) != -1) {
        (void)dup2(fd, STDIN_FILENO);
        (void)dup2(fd, STDOUT_FILENO);
        (void)dup2(fd, STDERR_FILENO);
        if (fd > 2)
            (void)close(fd);
    }
    return (0);
}
#endif /* NEEDS_DAEMON */

static void handle_sigs(void)  {
	int		sig;
	sigset_t			sigs;
	char		str[1024];
	FILE*	pidfile;

	thread_up(TRUE,TRUE);

	/* Write the standard .pid file if running as a daemon */
	/* Must be here so signals are sent to the correct thread */
	if(is_daemon)  {
		pidfile=fopen(SBBS_PID_FILE,"w");
		if(pidfile!=NULL) {
			fprintf(pidfile,"%d",getpid());
			fclose(pidfile);
		}
	}

	/* Set up blocked signals */
	sigemptyset(&sigs);
	sigaddset(&sigs,SIGINT);
	sigaddset(&sigs,SIGQUIT);
	sigaddset(&sigs,SIGABRT);
	sigaddset(&sigs,SIGTERM);
	sigaddset(&sigs,SIGHUP);
	sigaddset(&sigs,SIGALRM);
	sigaddset(&sigs,SIGPIPE);
	pthread_sigmask(SIG_BLOCK,&sigs,NULL);
	while(1)  {
		sigwait(&sigs,&sig);    /* wait here until signaled */
		sprintf(str,"     Got signal (%d)",sig);
		lputs(str);
		switch(sig)  {
			/* QUIT-type signals */
			case SIGINT:
			case SIGQUIT:
			case SIGABRT:
			case SIGTERM:
				_sighandler_quit(sig);
				break;
			case SIGHUP:    /* rerun */
				_sighandler_rerun(sig);
				break;
			default:
				sprintf(str,"     Signal has no handler (unexpected)");
				lputs(str);
		}
	}
}
#endif	/* __unix__ */

/****************************************************************************/
/* Main Entry Point															*/
/****************************************************************************/
int main(int argc, char** argv)
{
	int		i;
	int		n;
	int		file;
	char	ch;
	char*	p;
	char*	arg;
	char*	ctrl_dir;
	char	str[MAX_PATH+1];
    char	error[256];
	char	ini_file[MAX_PATH+1];
	char	host_name[128]="";
	BOOL	quit=FALSE;
	FILE*	fp=NULL;
	node_t	node;
#ifdef __unix__
	char	daemon_type[2];
	char	value[MAX_VALUE_LEN];
	struct passwd* pw_entry;
	struct group*  gr_entry;
	sigset_t			sigs;
#endif

#ifdef __QNX__
	setlocale( LC_ALL, "C-TRADITIONAL" );
#endif
#ifdef __unix__
	umask(077);
#endif
	printf("\nSynchronet Console for %s  Version %s%c  %s\n\n"
		,PLATFORM_DESC,VERSION,REVISION,COPYRIGHT_NOTICE);

	ctrl_dir=getenv("SBBSCTRL");	/* read from environment variable */
	if(ctrl_dir==NULL || ctrl_dir[0]==0) {
		ctrl_dir="/sbbs/ctrl";		/* Not set? Use default */
		printf("!SBBSCTRL environment variable not set, using default value: %s\n\n"
			,ctrl_dir);
	}

	if(!winsock_startup())
		return(-1);

	gethostname(host_name,sizeof(host_name)-1);

	if(!winsock_cleanup())
		return(-1);

	sprintf(ini_file,"%s%c%s.ini",ctrl_dir,BACKSLASH,host_name);
#if defined(__unix__) && defined(PREFIX)
	if(!fexistcase(ini_file))
		sprintf(ini_file,"%s/etc/sbbs.ini",PREFIX);
#endif
	if(!fexistcase(ini_file))
		sprintf(ini_file,"%s%csbbs.ini",ctrl_dir,BACKSLASH);

	/* Initialize BBS startup structure */
    memset(&bbs_startup,0,sizeof(bbs_startup));
    bbs_startup.size=sizeof(bbs_startup);

	bbs_startup.lputs=bbs_lputs;
	bbs_startup.event_log=event_lputs;
    bbs_startup.started=bbs_started;
    bbs_startup.terminated=bbs_terminated;
    bbs_startup.thread_up=thread_up;
    bbs_startup.socket_open=socket_open;
    bbs_startup.client_on=client_on;
#ifdef __unix__
	bbs_startup.seteuid=do_seteuid;
#endif
/*	These callbacks haven't been created yet
    bbs_startup.status=bbs_status;
    bbs_startup.clients=bbs_clients;
*/
    strcpy(bbs_startup.ctrl_dir,ctrl_dir);

	/* Initialize FTP startup structure */
    memset(&ftp_startup,0,sizeof(ftp_startup));
    ftp_startup.size=sizeof(ftp_startup);
	ftp_startup.lputs=ftp_lputs;
    ftp_startup.started=ftp_started;
    ftp_startup.terminated=ftp_terminated;
	ftp_startup.thread_up=thread_up;
    ftp_startup.socket_open=socket_open;
    ftp_startup.client_on=client_on;
#ifdef __unix__
	ftp_startup.seteuid=do_seteuid;
#endif
    strcpy(ftp_startup.index_file_name,"00index");
    strcpy(ftp_startup.ctrl_dir,ctrl_dir);

	/* Initialize Web Server startup structure */
    memset(&web_startup,0,sizeof(web_startup));
    web_startup.size=sizeof(web_startup);
	web_startup.lputs=web_lputs;
    web_startup.started=web_started;
    web_startup.terminated=web_terminated;
	web_startup.thread_up=thread_up;
    web_startup.socket_open=socket_open;
#ifdef __unix__
	web_startup.seteuid=do_seteuid;
#endif
    strcpy(web_startup.ctrl_dir,ctrl_dir);

	/* Initialize Mail Server startup structure */
    memset(&mail_startup,0,sizeof(mail_startup));
    mail_startup.size=sizeof(mail_startup);
	mail_startup.lputs=mail_lputs;
    mail_startup.started=mail_started;
    mail_startup.terminated=mail_terminated;
	mail_startup.thread_up=thread_up;
    mail_startup.socket_open=socket_open;
    mail_startup.client_on=client_on;
#ifdef __unix__
	mail_startup.seteuid=do_seteuid;
#endif
    strcpy(mail_startup.ctrl_dir,ctrl_dir);

#ifdef __unix__	/* Look up DNS server address */
	{
		FILE*	fp;
		char*	p;
		char	str[128];

		if((fp=fopen("/etc/resolv.conf","r"))!=NULL) {
			while(!feof(fp)) {
				if(fgets(str,sizeof(str),fp)==NULL)
					break;
				truncsp(str);
				p=str;
				while(*p && *p<=' ') p++;	/* skip white-space */
				if(strnicmp(p,"nameserver",10)!=0) /* no match */
					continue;
				p+=10;	/* skip "nameserver" */
				while(*p && *p<=' ') p++;	/* skip more white-space */
				SAFECOPY(mail_startup.dns_server,p);
				break;
			}
			fclose(fp);
		}
	}
#endif /* __unix__ */

	/* Initialize Services startup structure */
    memset(&services_startup,0,sizeof(services_startup));
    services_startup.size=sizeof(services_startup);
	services_startup.lputs=services_lputs;
    services_startup.started=services_started;
    services_startup.terminated=services_terminated;
	services_startup.thread_up=thread_up;
    services_startup.socket_open=socket_open;
    services_startup.client_on=client_on;
#ifdef __unix__
	services_startup.seteuid=do_seteuid;
#endif
    strcpy(services_startup.ctrl_dir,ctrl_dir);

	/* Pre-INI command-line switches */
	for(i=1;i<argc;i++) {
		arg=argv[i];
		while(*arg=='-')
			arg++;
		if(strcspn(arg,"\\/")!=strlen(arg)) {
			strcpy(ini_file,arg);
			continue;
		}
		if(!stricmp(arg,"ni")) {
			ini_file[0]=0;
			break;
		}
	}

	/* Read .ini file here */
	if(ini_file[0]!=0 && (fp=fopen(ini_file,"r"))!=NULL) {
		sprintf(str,"Reading %s",ini_file);
		bbs_lputs(str);
	}

	prompt = "[Threads: %d  Sockets: %d  Clients: %d  Served: %lu] (?=Help): ";

	/* We call this function to set defaults, even if there's no .ini file */
	sbbs_read_ini(fp, 
		&run_bbs,		&bbs_startup,
		&run_ftp,		&ftp_startup, 
		&run_web,		&web_startup,
		&run_mail,		&mail_startup, 
		&run_services,	&services_startup);

	/* read/default any sbbscon-specific .ini keys here */
#if defined(__unix__)
	SAFECOPY(new_uid_name,iniGetString(fp,"UNIX","User","",value));
	SAFECOPY(new_gid_name,iniGetString(fp,"UNIX","Group","",value));
	is_daemon=iniGetBool(fp,"UNIX","Daemonize",FALSE);
	SAFECOPY(daemon_type,iniGetString(fp,"UNIX","LogFacility","U",value));
#endif			
	/* close .ini file here */
	if(fp!=NULL)
		fclose(fp);

	/* Post-INI command-line switches */
	for(i=1;i<argc;i++) {
		arg=argv[i];
		while(*arg=='-')
			arg++;
		if(strcspn(arg,"\\/")!=strlen(arg))	/* ini_file name */
			continue;
		if(!stricmp(arg,"defaults")) {
			printf("Default settings:\n");
			printf("\n");
			printf("Telnet server port:\t%u\n",bbs_startup.telnet_port);
			printf("Telnet first node:\t%u\n",bbs_startup.first_node);
			printf("Telnet last node:\t%u\n",bbs_startup.last_node);
			printf("Telnet server options:\t0x%08lX\n",bbs_startup.options);
			printf("FTP server port:\t%u\n",ftp_startup.port);
			printf("FTP server options:\t0x%08lX\n",ftp_startup.options);
			printf("Mail SMTP server port:\t%u\n",mail_startup.smtp_port);
			printf("Mail SMTP relay port:\t%u\n",mail_startup.relay_port);
			printf("Mail POP3 server port:\t%u\n",mail_startup.pop3_port);
			printf("Mail server options:\t0x%08lX\n",mail_startup.options);
			printf("Services options:\t0x%08lX\n",services_startup.options);
			return(0);
		}
		switch(toupper(*(arg++))) {
#ifdef __unix__
				case 'D': /* Run as daemon */
					is_daemon=TRUE;
					SAFECOPY(daemon_type,arg++);
				break;
#endif
			case 'T':	/* Telnet settings */
				switch(toupper(*(arg++))) {
					case '-':	
						run_bbs=FALSE;
						break;
					case 'D': /* debug output */
						bbs_startup.options|=BBS_OPT_DEBUG_TELNET;
						break;
					case 'A': /* Auto-logon via IP */
						bbs_startup.options|=BBS_OPT_AUTO_LOGON;
						break;
					case 'Q': /* No QWK events */
						bbs_startup.options|=BBS_OPT_NO_QWK_EVENTS;
						break;
					case 'C': /* Sysop available for chat */
						bbs_startup.options|=BBS_OPT_SYSOP_AVAILABLE;
						break;
					case 'O': /* Set options */
						bbs_startup.options=strtoul(arg,NULL,0);
						break;
					case 'P':
						bbs_startup.telnet_port=atoi(arg);
						break;
					case 'F':
						bbs_startup.first_node=atoi(arg);
						break;
					case 'L':
						bbs_startup.last_node=atoi(arg);
						break;
					default:
						printf(usage,argv[0]);
						return(0);
				}
				break;
			case 'R':	/* RLogin */
				bbs_startup.options|=BBS_OPT_ALLOW_RLOGIN;
				switch(toupper(*(arg++))) {
					case 'P':
						bbs_startup.rlogin_port=atoi(arg);
						break;
					case '2':
						bbs_startup.options|=BBS_OPT_USE_2ND_RLOGIN;
						break;
					default:
						printf(usage,argv[0]);
						return(0);
				}
				break;
			case 'F':	/* FTP */
				switch(toupper(*(arg++))) {
					case '-':	
						run_ftp=FALSE;
						break;
					case 'P':
						ftp_startup.port=atoi(arg);
						break;
					case 'O': /* Set options */
						ftp_startup.options=strtoul(arg,NULL,0);
						break;
					default:
						printf(usage,argv[0]);
						return(0);
				}
				break;
			case 'M':	/* Mail */
				switch(toupper(*(arg++))) {
					case '-':
						run_mail=FALSE;
						break;
					case 'O': /* Set options */
						mail_startup.options=strtoul(arg,NULL,0);
						break;
					case 'S':	/* SMTP/SendMail */
						if(isdigit(*arg)) {
							mail_startup.smtp_port=atoi(arg);
							break;
						}
						switch(toupper(*(arg++))) {
							case '-':
								mail_startup.options|=MAIL_OPT_NO_SENDMAIL;
								break;
							default:
								printf(usage,argv[0]);
								return(0);
						}
						break;
					case 'P':	/* POP3 */
						if(isdigit(*arg)) {
							mail_startup.pop3_port=atoi(arg);
							break;
						}
						switch(toupper(*(arg++))) {
							case '-':
								mail_startup.options&=~MAIL_OPT_ALLOW_POP3;
								break;
							default:
								printf(usage,argv[0]);
								return(0);
						}
						break;
					case 'R':	/* Relay */
						mail_startup.options|=MAIL_OPT_RELAY_TX;
						p=strchr(arg,':');	/* port specified */
						if(p!=NULL) {
							*p=0;
							mail_startup.relay_port=atoi(p+1);
						}
						SAFECOPY(mail_startup.relay_server,arg);
						break;
					case 'D':	/* DNS Server */
						SAFECOPY(mail_startup.dns_server,arg);
						break;
					case 'A':
						mail_startup.options|=MAIL_OPT_ALLOW_RELAY;
						break;
					default:
						printf(usage,argv[0]);
						return(0);
				}
				break;
			case 'S':	/* Services */
				switch(toupper(*(arg++))) {
					case '-':
						run_services=FALSE;
						break;
					case 'O': /* Set options */
						services_startup.options=strtoul(arg,NULL,0);
						break;
					default:
						printf(usage,argv[0]);
						return(0);
				}
				break;
			case 'G':	/* GET */
				switch(toupper(*(arg++))) {
					case 'I': /* Identity */
						bbs_startup.options|=BBS_OPT_GET_IDENT;
						ftp_startup.options|=BBS_OPT_GET_IDENT;
						mail_startup.options|=BBS_OPT_GET_IDENT;
						services_startup.options|=BBS_OPT_GET_IDENT;
						break;
					default:
						printf(usage,argv[0]);
						return(0);
				}
				break;
			case 'H':	/* Host */
				switch(toupper(*(arg++))) {
					case 'N':	/* Name */
						if(*arg) {
							SAFECOPY(bbs_startup.host_name,arg);
							SAFECOPY(ftp_startup.host_name,arg);
							SAFECOPY(mail_startup.host_name,arg);
							SAFECOPY(services_startup.host_name,arg);
						} else {
							SAFECOPY(bbs_startup.host_name,host_name);
							SAFECOPY(ftp_startup.host_name,host_name);
							SAFECOPY(mail_startup.host_name,host_name);
							SAFECOPY(services_startup.host_name,host_name);
						}
						printf("Setting hostname: %s\n",bbs_startup.host_name);
						break;
					default:
						printf(usage,argv[0]);
						return(0);
				}
				break;
			case 'U':	/* runtime UID */
				switch(toupper(*(arg++))) {
					case 'N': /* username */
#ifdef __unix__
						if(strlen(arg) > 1)
						{
							SAFECOPY(new_uid_name,arg);
						}
#endif			
						break;
					case 'G': /* groupname */
#ifdef __unix__
						if(strlen(arg) > 1)
						{
							SAFECOPY(new_gid_name,arg);
						}
#endif			
						break;
					default:
						printf(usage,argv[0]);
						return(0);
				}
				break;
			case 'N':	/* No */
				switch(toupper(*(arg++))) {
					case 'F':	/* FTP Server */
						run_ftp=FALSE;
						break;
					case 'M':	/* Mail Server */
						run_mail=FALSE;
						break;
					case 'S':	/* Services */
						run_services=FALSE;
						break;
					case 'T':	/* Telnet */
						run_bbs=FALSE;
						break;
					case 'E': /* No Events */
						bbs_startup.options		|=BBS_OPT_NO_EVENTS;
						break;
					case 'Q': /* No QWK events */
						bbs_startup.options		|=BBS_OPT_NO_QWK_EVENTS;
						break;
					case 'H':	/* Hostname lookup */
						bbs_startup.options		|=BBS_OPT_NO_HOST_LOOKUP;
						ftp_startup.options		|=BBS_OPT_NO_HOST_LOOKUP;
						mail_startup.options	|=BBS_OPT_NO_HOST_LOOKUP;
						services_startup.options|=BBS_OPT_NO_HOST_LOOKUP;
						break;
					case 'J':	/* JavaScript */
						bbs_startup.options		|=BBS_OPT_NO_JAVASCRIPT;
						ftp_startup.options		|=BBS_OPT_NO_JAVASCRIPT;
						mail_startup.options	|=BBS_OPT_NO_JAVASCRIPT;
						services_startup.options|=BBS_OPT_NO_JAVASCRIPT;
						break;
					case 'I':	/* no .ini file */
						break;
					case 'D':
#if defined(__unix__)
						is_daemon=FALSE;
#endif
						break;
					default:
						printf(usage,argv[0]);
						return(0);
				}
				break;
			case 'L':	/* Local */
				switch(toupper(*(arg++))) {
					case 'T': /* timezone */
						bbs_startup.options		|=BBS_OPT_LOCAL_TIMEZONE;
						ftp_startup.options		|=BBS_OPT_LOCAL_TIMEZONE;
						mail_startup.options	|=BBS_OPT_LOCAL_TIMEZONE;
						services_startup.options|=BBS_OPT_LOCAL_TIMEZONE;
						break;
					default:
						printf(usage,argv[0]);
						return(0);
				}
				break;

			default:
				printf(usage,argv[0]);
				return(0);
		}
	}

/* Daemonize / Set uid/gid */
#ifdef __unix__

	if(is_daemon) {
		switch(toupper(daemon_type[0])) {
			case '0':
				openlog(SBBS_LOG_NAME,LOG_CONS,LOG_LOCAL0);
				break;
			case '1':
				openlog(SBBS_LOG_NAME,LOG_CONS,LOG_LOCAL1);
				break;
			case '2':
				openlog(SBBS_LOG_NAME,LOG_CONS,LOG_LOCAL2);
				break;
			case '3':
				openlog(SBBS_LOG_NAME,LOG_CONS,LOG_LOCAL3);
				break;
			case '4':
				openlog(SBBS_LOG_NAME,LOG_CONS,LOG_LOCAL4);
				break;
			case '5':
				openlog(SBBS_LOG_NAME,LOG_CONS,LOG_LOCAL5);
				break;
			case '6':
				openlog(SBBS_LOG_NAME,LOG_CONS,LOG_LOCAL6);
				break;
			case '7':
				openlog(SBBS_LOG_NAME,LOG_CONS,LOG_LOCAL7);
				break;
			case 'F':	/* this is legacy */
			case 'S':
				/* Use standard facilities */
				openlog(SBBS_LOG_NAME,LOG_CONS,LOG_USER);
				std_facilities = TRUE;
				break;
			default:
				openlog(SBBS_LOG_NAME,LOG_CONS,LOG_USER);
		}

		printf("Running as daemon\n");
		if(daemon(TRUE,FALSE))  { /* Daemonize, DON'T switch to / and DO close descriptors */
			printf("!ERROR %d running as daemon",errno);
			is_daemon=FALSE;
		}
	}

	old_uid = getuid();
	if((pw_entry=getpwnam(new_uid_name))!=0)
	{
		new_uid=pw_entry->pw_uid;
		new_gid=pw_entry->pw_gid;
	}
	else  {
		new_uid=getuid();
		new_gid=getgid();
	}
	old_gid = getgid();
	if((gr_entry=getgrnam(new_gid_name))!=0)
		new_gid=gr_entry->gr_gid;
	
#endif

	/* Read in configuration files */
    memset(&scfg,0,sizeof(scfg));
    SAFECOPY(scfg.ctrl_dir,bbs_startup.ctrl_dir);
    scfg.size=sizeof(scfg);
	SAFECOPY(error,UNKNOWN_LOAD_ERROR);
	sprintf(str,"Loading configuration files from %s", scfg.ctrl_dir);
	bbs_lputs(str);
	if(!load_cfg(&scfg, NULL /* text.dat */, TRUE /* prep */, error)) {
		fprintf(stderr,"\n!ERROR Loading Configuration Files: %s", error);
        return(-1);
    }


#ifdef __unix__
	/* Set up blocked signals */
	sigemptyset(&sigs);
	sigaddset(&sigs,SIGINT);
	sigaddset(&sigs,SIGQUIT);
	sigaddset(&sigs,SIGABRT);
	sigaddset(&sigs,SIGTERM);
	sigaddset(&sigs,SIGHUP);
	sigaddset(&sigs,SIGALRM);
	sigaddset(&sigs,SIGPIPE);
	pthread_sigmask(SIG_BLOCK,&sigs,NULL);
    signal(SIGALRM, SIG_IGN);       /* Ignore "Alarm" signal */
	_beginthread((void(*)(void*))handle_sigs,0,NULL);
#endif

	if(run_bbs)
		_beginthread((void(*)(void*))bbs_thread,0,&bbs_startup);
	if(run_ftp)
		_beginthread((void(*)(void*))ftp_server,0,&ftp_startup);
	if(run_mail)
		_beginthread((void(*)(void*))mail_server,0,&mail_startup);
#ifdef JAVASCRIPT
	if(run_services)
		_beginthread((void(*)(void*))services_thread,0,&services_startup);
#endif
#ifdef WEB_SERVER
	if(run_web)
		_beginthread((void(*)(void*))web_server,0,&web_startup);
#endif

#ifdef __unix__
	if(getuid())  /*  are we running as a normal user?  */
		bbs_lputs("!Started as non-root user.  Cannot bind() to ports below 1024.");
	
	else if(new_uid_name[0]==0)   /*  check the user arg, if we have uid 0 */
		bbs_lputs("Warning: No user account specified, running as root.");
	
	else 
	{
		bbs_lputs("Waiting for child threads to bind ports...");
		while((run_bbs && !(bbs_running || bbs_stopped)) 
				|| (run_ftp && !(ftp_running || ftp_stopped)) 
				|| (run_web && !(web_running || web_stopped)) 
				|| (run_mail && !(mail_running || mail_stopped)) 
				|| (run_services && !(services_running || services_stopped)))  {
			mswait(1000);
			if(run_bbs && !(bbs_running || bbs_stopped))
				bbs_lputs("Waiting for BBS thread");
			if(run_web && !(web_running || web_stopped))
				bbs_lputs("Waiting for Web thread");
			if(run_ftp && !(ftp_running || ftp_stopped))
				bbs_lputs("Waiting for FTP thread");
			if(run_mail && !(mail_running || mail_stopped))
				bbs_lputs("Waiting for Mail thread");
			if(run_services && !(services_running || services_stopped))
				bbs_lputs("Waiting for Services thread");
		}

		if(!do_setuid())
				/* actually try to change the uid of this process */
			bbs_lputs("!Setting new user_id failed!  (Does the user exist?)");
	
		else {
			char str[256];
			sprintf(str,"Successfully changed user_id to %s", new_uid_name);
			bbs_lputs(str);

			/* Can't recycle servers (re-bind ports) as non-root user */
			/* ToDo: Something seems to be broken here on FreeBSD now */
			/* ToDo: Now, they try to re-bind on FreeBSD */
			/* ToDo: Seems like I switched problems with Linux */
 			bbs_startup.options|=BBS_OPT_NO_RECYCLE;
			ftp_startup.options|=FTP_OPT_NO_RECYCLE;
			mail_startup.options|=MAIL_OPT_NO_RECYCLE;
			services_startup.options|=BBS_OPT_NO_RECYCLE;
		}
	}

	if(!isatty(fileno(stdin)))  			/* redirected */
		select(0,NULL,NULL,NULL,NULL);	/* Sleep forever - Should this just exit the thread? */
	else								/* interactive */
#endif
		while(!quit) {
			ch=getch();
			printf("%c\n",ch);
			switch(ch) {
				case 'q':
					quit=TRUE;
					break;
				case 'w':	/* who's online */
					printf("\nNodes in use:\n");
				case 'n':	/* nodelist */
					printf("\n");
					for(i=1;i<=scfg.sys_nodes;i++) {
						getnodedat(&scfg,i,&node,NULL /* file */);
						if(ch=='w' && node.status!=NODE_INUSE && node.status!=NODE_QUIET)
							continue;
						printnodedat(&scfg, i,&node);
					}
					break;
				case 'l':	/* lock node */
				case 'd':	/* down node */
				case 'i':	/* interrupt node */
					printf("\nNode number: ");
					if((n=atoi(fgets(str,sizeof(str),stdin)))<1)
						break;
					fflush(stdin);
					printf("\n");
					if((i=getnodedat(&scfg,n,&node,&file))!=0) {
						printf("!Error %d getting node %d data\n",i,n);
						break;
					}
					switch(ch) {
						case 'l':
							node.misc^=NODE_LOCK;
							break;
						case 'd':
							node.misc^=NODE_DOWN;
							break;
						case 'i':
							node.misc^=NODE_INTR;
							break;
					}
					putnodedat(&scfg,n,&node,file);
					printnodedat(&scfg,n,&node);
					break;
				default:
					printf("\nSynchronet Console Version %s%c Help\n\n",VERSION,REVISION);
					printf("q   = quit\n");
					printf("n   = node list\n");
					printf("w   = who's online\n");
					printf("l   = lock node (toggle)\n");
					printf("d   = down node (toggle)\n");
					printf("i   = interrupt node (toggle)\n");
#if 0	/* to do */	
					printf("c#  = chat with node #\n");
					printf("s#  = spy on node #\n");
#endif
					break;
			}
			lputs("");	/* redisplay prompt */
		}

	terminate();

	/* erase the prompt */
	printf("\r%*s\r",prompt_len,"");

	return(0);
}