nuts4_141/
nuts4_141/n4/boards/0002/
nuts4_141/n4/boards/0003/
nuts4_141/n4/boards/0004/
nuts4_141/n4/boards/0006/
nuts4_141/n4/boards/0FFF/
nuts4_141/n4/etc/
nuts4_141/n4/pubgroups/0004/
nuts4_141/n4/pubgroups/0006/
nuts4_141/n4/users/0FFF/
nuts4_141/n4b/boards/0001/
nuts4_141/n4b/boards/0002/
nuts4_141/n4b/boards/0FFF/
nuts4_141/n4b/etc/
nuts4_141/n4b/pubgroups/0004/
nuts4_141/n4b/pubgroups/0005/
nuts4_141/n4b/src/
nuts4_141/n4b/users/0FFF/
/***************************************************************************
 FILE: main.cc
 LVU : 1.4.0

 DESC:
 This file contains the main function, the initialisation and system 
 configuration parse code, and the main loop (for both user and server input).

 Copyright (C) Neil Robertson 2003-2005

  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

 ***************************************************************************/

#define MAINFILE
#include "globals.h"


// Module variables
static fd_set readmask; 
static int suppress_config_info;
static struct sigaction sigact;


// Forward declarations
void parse_command_line(int argc, char **argv);
void init();
void parse_config_file();
int  parse_config_integer(cl_splitline *sl, int linenum);
int  parse_config_time(cl_splitline *sl,int linenum, int gzero);
int  parse_config_level(cl_splitline *sl,int linenum);
int  parse_config_yn(cl_splitline *sl,int linenum);
void parse_remote_server_line(cl_splitline *sl,int linenum);
void parse_signal_ignore(cl_splitline *sl, int linenum);
void load_bans();
int load_user_ban(int ban_level,cl_splitline *sl);
int load_site_ban(int ban_level,cl_splitline *sl);
void create_system_groups();
void load_public_groups();
void spawn_all_threads();
void mainloop();
void setup_readmask();
void get_input();

extern "C" { // Stops Solaris compiler whining
void signal_handler(int signum, siginfo_t *siginfo, void *notused);
}



/******** Start here *********/
int main(int argc, char **argv)
{
// Set for reboot execvp()
global_argv = argv;

strcpy(build,"<default>");

#ifdef LINUX
strcpy(build,"LINUX");
#endif

#ifdef BSD
if (build[0] != '<') {
	puts("ERROR: Multiple -D <OS> lines passed to compiler! Fix Makefile and recompile.");
	exit(1);
	}
strcpy(build,"BSD");
#endif

#ifdef SOLARIS
if (build[0] != '<') {
	puts("ERROR: Multiple -D <OS> lines passed to compiler! Fix Makefile and recompile.");
	exit(1);
	}
strcpy(build,"SOLARIS");
#endif

#ifdef _AIX
// This is defined by the OS , not the makefile
strcpy(build,"AIX");
#endif

parse_command_line(argc,argv);

log(0,"\n@~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~)\n");
log(0," | NUTS-IV version   : %s              |\n",VERSION);
log(0," | NIVN protocol rev.: %d                  |\n",PROTOCOL_REVISION);
log(0," | Build             : %-9s          |\n",build);
log(0," | Process id        : %-5d              |\n",main_pid);
log(0," |                                        |\n");
log(0," | Copyright (C) Neil Robertson 2003-2005 |\n");
log(0,"@__________________________________________)\n\n");

init();
spawn_all_threads();
mainloop();
}




/*** Go through the command line arguments ***/
void parse_command_line(int argc, char **argv)
{
struct stat fs;
char *cf;
int i,len;

config_file = CONFIG_FILE;
be_daemon = 0;
logfile = NULL;
suppress_config_info = 0;

for(i=1;i < argc;++i) {
	if (argv[i][0] != '-' || strlen(argv[i]) != 2) goto USAGE;

	switch(argv[i][1]) {
		case 'c':
		if (i == argc - 1) goto USAGE;
		cf = argv[++i];

		// If we don't have a config extension on the end then add it
		len = strlen(cf);
		config_file = (char *)malloc(len + 5);
		if (len > 3 && !strcmp(cf+len-4,CONFIG_EXT))
			strcpy(config_file,cf);
		else sprintf(config_file,"%s%s",cf,CONFIG_EXT);
		break;

		case 'd': be_daemon = 1;  break;

		case 'l':
		if (i == argc - 1) goto USAGE;
		logfile = argv[++i];
		break;
	
		case 'V':
		printf("NUTS-IV version   : %s\n",VERSION);
		printf("NIVN protocol rev.: %d\n",PROTOCOL_REVISION);
		printf("Build             : %s\n",build);
		exit(0);

		case 's':
		suppress_config_info = 1;
		break;


		default: goto USAGE;
		}
	}

/* Make sure logfile if it already exists is a normal file as it might hang
   the talker in the viewlog command if it isn't - eg /dev/tty. Make an
   exception for /dev/null. */
if (logfile && strcmp(logfile,"/dev/null")) {
	if (stat(logfile,&fs) != -1 && (fs.st_mode & S_IFMT) != S_IFREG) {
		puts("ERROR: The log file must either be a regular file or /dev/null.");
		exit(1);
		}
	// Clear old logfile
	unlink(logfile);
	}

// Fork off. So to speak. More tea vicar?
if (be_daemon) {
	if (!logfile) {
		puts("ERROR: You must specify a log file if the server is to be run as a background daemon.");
		exit(1);
		}
	// Fork here before we spawn threads since POSIX threads are not
	// duplicated in a fork call , plus it would get messy anyway.
	switch(fork()) {
		case -1:
		perror("ERROR: fork()");  exit(1);

		case 0:  break;

		default: exit(0);
		}
	}

main_pid = getpid();
return;

USAGE:
printf("Usage: %s [-h] [-V] [-c <config file>] [-l logfile [-d]] [-s]\n",
	argv[0]);
exit(1);
}




/*** Initialise (almost) everything ***/
void init()
{
sigset_t set;
int i,ret;
char *v;

booting = 1;

// 'v' is a hack since gcc bombs out with internal error if we use the macro.
// Solaris compilers don't care though. Oh well.
v=VERSION;
svr_version = (v[0]-'0')*100 + (v[2]-'0')*10 + (v[4]-'0');

// Set locale to default unix behaviour just in case this is 
// being run in some johnny foreigner place ;)
setlocale(LC_ALL, "C");

// Initialise globals
bzero((char *)&main_bind_addr,sizeof(main_bind_addr));
main_bind_addr.sin_family = AF_INET;
main_bind_addr.sin_addr.s_addr = INADDR_ANY;

if (!(original_dir = getcwd(NULL,256))) {
	perror("ERROR: getcwd()");  exit(1);
	}
working_dir = WORKING_DIR;
system_flags = DEFAULT_SYSTEM_FLAGS;
first_user = NULL;
last_user = NULL;
first_user_ban = NULL;
last_user_ban = NULL;
first_site_ban = NULL;
last_site_ban = NULL;
first_group = NULL;
last_group = NULL;
first_server = NULL;
last_server = NULL;
first_thread = NULL;
last_thread = NULL;
boot_time = server_time = time(0);
localtime_r(&server_time,&server_time_tms);
server_count = 0;
connected_server_count = 0;
shutdown_time = 0;
shutdown_type = SHUTDOWN_INACTIVE;
ansi_terms = NULL;
tcp_port[PORT_TYPE_USER] = USER_PORT;
tcp_port[PORT_TYPE_SERVER] = SERVER_PORT;
listen_sock[PORT_TYPE_USER] = -1;
listen_sock[PORT_TYPE_SERVER] = -1;
local_connect_key = 0;
server_name[0] = '\0';
srandom((u_int)time(0));
thread_count = 0;
max_local_users = MAX_LOCAL_USERS;
max_remote_users = MAX_REMOTE_USERS;
max_user_ign_level = MAX_USER_IGN_LEVEL;
local_user_count = 0;
remote_user_count = 0;
max_name_len = MAX_NAME_LEN;
max_desc_len = MAX_DESC_LEN;
min_pwd_len = MIN_PWD_LEN;
send_ping_interval = PING_INTERVAL;
server_timeout = SERVER_TIMEOUT;
connect_timeout = CONNECT_TIMEOUT;
linkdead_timeout = LINKDEAD_TIMEOUT;
login_timeout = LOGIN_TIMEOUT;
idle_timeout = IDLE_TIMEOUT;
idle_timeout_ign_level = IDLE_TIMEOUT_IGN_LEVEL;
max_hop_cnt = MAX_HOP_CNT;
max_tx_errors = MAX_TX_ERR_CNT;
max_rx_errors = MAX_RX_ERR_CNT;
max_packet_rate = MAX_PACKET_RATE;
max_local_user_data_rate = MAX_LOCAL_USER_DATA_RATE;
max_svr_data_rate = MAX_SVR_DATA_RATE;
max_profile_chars = MAX_PROFILE_CHARS;
max_mail_chars = MAX_MAIL_CHARS;
max_board_chars = MAX_BOARD_CHARS;
max_group_desc_chars = MAX_GROUP_DESC_CHARS;
max_broadcast_chars = MAX_BROADCAST_CHARS;
max_subject_len = MAX_SUBJECT_LEN;
max_group_name_len = MAX_GROUP_NAME_LEN;
max_batch_name_len = MAX_BATCH_NAME_LEN;
max_login_batch_lines = MAX_LOGIN_BATCH_LINES;
max_logout_batch_lines = MAX_LOGOUT_BATCH_LINES;
max_session_batch_lines = MAX_SESSION_BATCH_LINES;
max_include_lines = MAX_INCLUDE_LINES;
default_desc = strdup(DEFAULT_DESC);
default_pwd = strdup(DEFAULT_PWD);
lockout_level = LOCKOUT_LEVEL;
group_modify_level = GROUP_MODIFY_LEVEL;
group_gatecrash_level = GROUP_GATECRASH_LEVEL;
group_invite_expire = GROUP_INVITE_EXPIRE;
remote_user_max_level = REMOTE_USER_MAX_LEVEL;
num_review_lines = NUM_REVIEW_LINES;
board_msg_expire = BOARD_MSG_EXPIRE;
go_invis_level = GO_INVIS_LEVEL;
autosave_interval = AUTOSAVE_INTERVAL;
autosave_time = server_time;
recv_net_bcast_level = RECV_NET_BCAST_LEVEL;
incoming_encryption_policy = ENCRYPT_EITHER;
soft_max_servers = SOFT_MAX_SERVERS;

/* Parse data files and create system groups. System groups must be created
   after config file is parses since they require the working directory path
   to load their data files just like public & user groups */
parse_config_file();
load_bans();
create_system_groups();
load_public_groups();

// Init sockets 
log(0,"\nSetting up TCP ports...");

log(0,"   Initialising user port %d...\n",tcp_port[PORT_TYPE_USER]);
if ((ret = create_listen_socket(PORT_TYPE_USER)) != OK) {
	printf("ERROR: Unable to open port: %s: %s\n",
		err_string[ret],strerror(errno));
	exit(1);
	}
if (SYS_FLAGISSET(SYS_FLAG_SVR_LISTEN)) {
	log(0,"   Initialising server port %d...\n",tcp_port[PORT_TYPE_SERVER]);
	if ((ret = create_listen_socket(PORT_TYPE_SERVER)) != OK) {
		printf("ERROR: Unable to open port: %s: %s\n",
			err_string[ret],strerror(errno));
		exit(1);
		}
	}
else log(0,"   Server port disabled.\n");
log(0,"Done.\n");

// Set up mutexes
pthread_mutex_init(&log_mutex,NULL);
pthread_mutex_init(&events_mutex,NULL);
pthread_mutex_init(&threadlist_mutex,NULL);

/* Set up signal handlers. Always ignore sigpipes since we can get these from
   duff write()'s and we don't want the server crashing out just because of
   that. Ignore SIGWINCH since its irrelevant to us but could cause problems
   if not done. */
signal(SIGPIPE,SIG_IGN);
signal(SIGWINCH,SIG_IGN);

sigact.sa_handler = NULL;
sigact.sa_sigaction = signal_handler;
sigemptyset(&set);
sigact.sa_mask = set;
sigact.sa_flags = SA_SIGINFO; // So we use sa_sigaction func, not handler func.

for(i=0;i < NUM_SIGNALS;++i) sigaction(siglist[i].num,&sigact,NULL);
}




/*** Go through the server config file and set the params accordingly ***/
void parse_config_file()
{
cl_splitline sl(1);
FILE *fp;
int i,ret,len,linenum,err;
char line[ARR_SIZE];
char path[MAXPATHLEN];
char *config_option[]={ 
	"server name",
	"working dir",
	"user port",
	"server port",
	"server port listening",
	"connect key",
	"ansi terminals",
	"max name length",
	"max desc length",
	"min pwd length",
	"ping interval",
	"server timeout",
	"connect timeout",
	"server",
	"linkdead timeout",
	"login timeout",
	"idle timeout",
	"idle timeout ignore level",
	"max local users",
	"max remote users",
	"max user ignore level",
	"max hop",
	"max tx errors",
	"max rx errors",
	"max profile chars",
	"max mail chars",
	"max board chars",
	"max group desc chars",
	"max group name length",
	"max include lines",
	"max broadcast chars",
	"allow loopback",
	"random remote ids",
	"default desc",
	"default pwd",
	"group modify level",
	"group gatecrash level",
	"group invite expire",
	"lockout level",
	"remote user max level",
	"review lines",
	"board msg expire",
	"board renumber",
	"max subject length",
	"signal ignore",
	"max login batch lines",
	"max logout batch lines",
	"max session batch lines",
	"max batch name length",
	"go invis level",
	"prisoners ret home",
	"bind interface",
	"autosave interval",
	"log net broadcasts",
	"recv net bcast level",
	"hexdump packets",
	"strip printcodes",
	"allow who at login",
	"allow remote batch runs",
	"allow new accounts",
	"delete disconnected incoming",
	"log unexpected packets",
	"max packet rate",
	"incoming encryption policy",
	"outgoing encryption enforce",
	"soft max servers",
	"resolve ip name internally",
	"max local user data rate",
	"max server data rate",
	"save novice accounts",
	"really delete accounts",
	"log groups"
	};

// Not really need but makes the code easier to read
enum {
	OPT_SERVER_NAME,
	OPT_WORKING_DIR,
	OPT_USER_PORT,
	OPT_SERVER_PORT,
	OPT_SERVER_PORT_LISTENING,
	OPT_CONNECT_KEY,
	OPT_ANSI_TERMS,
	OPT_MAX_NAME_LEN,
	OPT_MAX_DESC_LEN,
	OPT_MIN_PWD_LEN,
	OPT_PING_INTERVAL,
	OPT_SERVER_TIMEOUT,
	OPT_CONNECT_TIMEOUT,
	OPT_REMOTE_SERVER,
	OPT_LINKDEAD_TIMEOUT,
	OPT_LOGIN_TIMEOUT,
	OPT_IDLE_TIMEOUT,
	OPT_IDLE_TIMEOUT_IGN_LEVEL,
	OPT_MAX_LOCAL_USERS,
	OPT_MAX_REMOTE_USERS,
	OPT_MAX_USER_IGN_LEVEL,
	OPT_MAX_HOP,
	OPT_MAX_TX_ERRS,
	OPT_MAX_RX_ERRS,
	OPT_MAX_PROFILE_CHARS,
	OPT_MAX_MAIL_CHARS,
	OPT_MAX_BOARD_CHARS,
	OPT_MAX_GROUP_DESC_CHARS,
	OPT_MAX_GROUP_NAME_LEN,
	OPT_MAX_INCLUDE_LINES,
	OPT_MAX_BROADCAST_CHARS,
	OPT_ALLOW_LB_USERS,
	OPT_ALLOW_RR_ID,
	OPT_DEFAULT_DESC,
	OPT_DEFAULT_PWD,
	OPT_GROUP_MODIFY_LEVEL,
	OPT_GATECRASH_LEVEL,
	OPT_INVITE_EXPIRE,
	OPT_LOCKOUT_LEVEL,
	OPT_REMOTE_USER_MAX_LEVEL,
	OPT_REVIEW_LINES,
	OPT_BOARD_MSG_EXPIRE,
	OPT_BOARD_RENUMBER,
	OPT_MAX_SUBJECT_LEN,
	OPT_SIGNAL_IGNORE,
	OPT_MAX_LOGIN_LINES,
	OPT_MAX_LOGOUT_LINES,
	OPT_MAX_SESSION_LINES,
	OPT_MAX_BATCH_NAME_LEN,
	OPT_GO_INVIS_LEVEL,
	OPT_PRISONERS_RET_HOME,
	OPT_BIND_INTERFACE,
	OPT_AUTOSAVE_INTERVAL,
	OPT_LOG_NET_BROADCASTS,
	OPT_RECV_NET_BCAST_LEVEL,
	OPT_HEXDUMP_PACKETS,
	OPT_STRIP_PRINTCODES,
	OPT_ALLOW_WHO_AT_LOGIN,
	OPT_ALLOW_REM_BATCH_RUNS,
	OPT_ALLOW_NEW_ACCOUNTS,
	OPT_DEL_DISC_INCOMING,
	OPT_LOG_UX_PACKETS,
	OPT_MAX_PACKET_RATE,
	OPT_INC_ENCRYPT_POL,
	OPT_OUT_ENFORCE_ENCRYPTION,
	OPT_SOFT_MAX_SERVERS,
	OPT_INTERNAL_RESOLVE,
	OPT_MAX_USER_DATA_RATE,
	OPT_MAX_SERVER_DATA_RATE,
	OPT_SAVE_NOVICE_ACCOUNTS,
	OPT_REALLY_DELETE_ACCOUNTS,
	OPT_LOG_GROUPS,

	OPT_END
	};

log(0,"Loading configuration data from file \"%s\"...\n",config_file);

if (!(fp=fopen(config_file,"r"))) {
	log(0,"ERROR: Cannot open config file '%s': %s\n",
		config_file,strerror(errno));
	exit(1);
	}

err = 0;
linenum=1;
fgets(line,ARR_SIZE-1,fp);

while(!feof(fp) && !(err = ferror(fp))) {
	if ((ret=sl.parse(line)) != OK) {
		log(0,"ERROR: %s on line %d.\n",err_string[ret],linenum);
		exit(1);
		}
	if (sl.wcnt) {
		for(i=0;i < OPT_END;++i)
			if (!strcmp(sl.word[0],config_option[i])) break;

		switch(i) {
			case OPT_SERVER_NAME:
			if (sl.wcnt > 2) goto EXTRA;
			if (!(len=strlen(sl.word[1]))) {
				log(0,"ERROR: Server name is null on line %d.\n",linenum);
				exit(1);
				}
			if (len > MAX_SERVER_NAME_LEN) {
				log(0,"ERROR: Server name is too long on line %d.\n",linenum);
				exit(1);
				}
			// Server name can only be one word
			if (has_whitespace(sl.word[1])) {
				log(0,"ERROR: Server name cannot contain whitespace on line %d.\n",linenum);
				exit(1);
				}
			strcpy(server_name,sl.word[1]);
			break;


			case OPT_WORKING_DIR:
			if (sl.wcnt > 2) goto EXTRA;
			working_dir = strdup(sl.word[1]);
			len = strlen(working_dir)-1;
			if (working_dir[len] == '/') working_dir[len] = '\0';
			break;


			case OPT_USER_PORT:
			if (sl.wcnt > 2) goto EXTRA;
			if (!is_integer(sl.word[1]) || 
			    (tcp_port[PORT_TYPE_USER]=atoi(sl.word[1])) < 1 || tcp_port[PORT_TYPE_USER] > 65535) {
				log(0,"ERROR: Invalid user port number '%s' on line %d. Range is 1 to 65535.\n",sl.word[1],linenum);
				exit(1);
				}
			break;


			case OPT_SERVER_PORT:
			if (sl.wcnt > 2) goto EXTRA;
			if (!is_integer(sl.word[1]) || 
			    (tcp_port[PORT_TYPE_SERVER]=atoi(sl.word[1])) < 1 || tcp_port[PORT_TYPE_SERVER] > 65535) {
				log(0,"ERROR: Invalid server port number '%s' on line %d. Range is 1 to 65535.\n",sl.word[1],linenum);
				exit(1);
				}
			break;


			case OPT_SERVER_PORT_LISTENING:
			if (parse_config_yn(&sl,linenum))
				SYS_SETFLAG(SYS_FLAG_SVR_LISTEN);
			else 
				SYS_UNSETFLAG(SYS_FLAG_SVR_LISTEN);
			break;


			case OPT_CONNECT_KEY:
			local_connect_key = parse_config_integer(&sl,linenum);
			break;


			case OPT_ANSI_TERMS:
			if (ansi_terms) 
				log(0,"WARNING: Ansi terminals redefined on line %d.\n",linenum);
			else ansi_terms=new cl_splitline(0);

			if ((ret=ansi_terms->set(&sl)) != OK) {
				log(0,"ERROR: parse_config_file() -> cl_splitline::set(): %s\n",err_string[ret]);
				exit(1);
				}
			break;


			case OPT_MAX_NAME_LEN:
			if (sl.wcnt > 2) goto EXTRA;

			// 3 seems a realistic min value
			if (!is_integer(sl.word[1]) ||
			    (max_name_len=atoi(sl.word[1])) < 3 || 
			    max_name_len > 255) {
				log(0,"ERROR: Invalid name length '%s' on line %d. Range is 3 to 255 characters.\n",sl.word[1],linenum);
				exit(1);
				}
			break;


			case OPT_MAX_DESC_LEN:
			if (sl.wcnt > 2) goto EXTRA;

			if (!is_integer(sl.word[1]) ||
			    (max_desc_len=atoi(sl.word[1])) < 0 ||
			    max_desc_len > 255) {
				log(0,"ERROR: Invalid description length '%s' on line %d. Range is 0 to 255 characters.\n",sl.word[1],linenum);
				exit(1);
				}
			break;


			case OPT_MIN_PWD_LEN:
			if (sl.wcnt > 2) goto EXTRA;
			if (!is_integer(sl.word[1]) ||
			    (min_pwd_len=atoi(sl.word[1])) < 3) {
				log(0,"ERROR: Invalid length '%s' on line %d.\n",sl.word[1],linenum);
				exit(1);
				}
			break;


			case OPT_PING_INTERVAL:
			if (!(send_ping_interval = parse_config_time(&sl,linenum,1))) {
				log(0,"ERROR: Ping interval must be greater than zero.");
				exit(1);
				}
			break;


			case OPT_SERVER_TIMEOUT:
			server_timeout = parse_config_time(&sl,linenum,1);
			break;


			case OPT_CONNECT_TIMEOUT:
			connect_timeout = parse_config_time(&sl,linenum,1);
			break;


			case OPT_REMOTE_SERVER:
			if (sl.wcnt > 7) goto EXTRA;
			parse_remote_server_line(&sl,linenum);
			break;


			case OPT_LINKDEAD_TIMEOUT:
			linkdead_timeout = parse_config_time(&sl,linenum,1);
			break;


			case OPT_LOGIN_TIMEOUT:
			login_timeout = parse_config_time(&sl,linenum,0);
			break;
			

			case OPT_IDLE_TIMEOUT:
			idle_timeout = parse_config_time(&sl,linenum,0);
			break;
			

			case OPT_IDLE_TIMEOUT_IGN_LEVEL:
			idle_timeout_ign_level = parse_config_level(&sl,linenum);
			break;


			case OPT_MAX_LOCAL_USERS:
			max_local_users = parse_config_integer(&sl,linenum);
			break;


			case OPT_MAX_REMOTE_USERS:
			max_remote_users = parse_config_integer(&sl,linenum);
			break;


			case OPT_MAX_USER_IGN_LEVEL:
			max_user_ign_level = parse_config_level(&sl,linenum);
			break;


			case OPT_MAX_HOP:
			if (sl.wcnt > 2) goto EXTRA;
			/* Max is 254 since if it was 255 then when 1 is added
			   to it it rolls back to zero since in packet its an
			   unsigned char */
			if (!is_integer(sl.word[1]) || 
			    (max_hop_cnt=atoi(sl.word[1])) < 1 || max_hop_cnt > 254) {
				log(0,"ERROR: Invalid max hop count '%s' on line %d. Range is 1 to 254.\n",sl.word[1],linenum);
				exit(1);
				}
			break;


			case OPT_MAX_TX_ERRS:
			max_tx_errors = parse_config_integer(&sl,linenum);
			break;


			case OPT_MAX_RX_ERRS:
			max_rx_errors = parse_config_integer(&sl,linenum);
			break;


			case OPT_MAX_PROFILE_CHARS:
			max_profile_chars = parse_config_integer(&sl,linenum);
			break;


			case OPT_MAX_MAIL_CHARS:
			max_mail_chars = parse_config_integer(&sl,linenum);
			break;


			case OPT_MAX_BOARD_CHARS:
			max_board_chars = parse_config_integer(&sl,linenum);
			break;


			case OPT_MAX_GROUP_DESC_CHARS:
			max_group_desc_chars = parse_config_integer(&sl,linenum);
			break;


			case OPT_MAX_GROUP_NAME_LEN:
			max_group_name_len = parse_config_integer(&sl,linenum);
			break;


			case OPT_ALLOW_LB_USERS:
			if (parse_config_yn(&sl,linenum))
				SYS_SETFLAG(SYS_FLAG_LOOPBACK_USERS);
			else 
				SYS_UNSETFLAG(SYS_FLAG_LOOPBACK_USERS);
			break;


			case OPT_ALLOW_RR_ID:
			if (parse_config_yn(&sl,linenum))
				SYS_SETFLAG(SYS_FLAG_RANDOM_REM_IDS);
			else
				SYS_UNSETFLAG(SYS_FLAG_RANDOM_REM_IDS);
			break;


			case OPT_DEFAULT_DESC:
			if (sl.wcnt > 2) goto EXTRA;
			free(default_desc);
			default_desc = strdup(sl.word[1]);
			break;


			case OPT_DEFAULT_PWD:
			if (sl.wcnt > 2) goto EXTRA;
			free(default_pwd);
			default_pwd = strdup(sl.word[1]);
			break;


			case OPT_GROUP_MODIFY_LEVEL:
			group_modify_level = parse_config_level(&sl,linenum);
			break;


			case OPT_GATECRASH_LEVEL:
			group_gatecrash_level = parse_config_level(&sl,linenum);
			break;


			case OPT_INVITE_EXPIRE:
			group_invite_expire = parse_config_time(&sl,linenum,0);
			break;


			case OPT_LOCKOUT_LEVEL:
			lockout_level = parse_config_level(&sl,linenum);
			break;


			case OPT_REMOTE_USER_MAX_LEVEL:
			remote_user_max_level = parse_config_level(&sl,linenum);
			break;


			case OPT_REVIEW_LINES:
			num_review_lines = parse_config_integer(&sl,linenum);
			break;


			case OPT_BOARD_MSG_EXPIRE:
			board_msg_expire = parse_config_time(&sl,linenum,0);
			break;


			case OPT_BOARD_RENUMBER:
			if (parse_config_yn(&sl,linenum))
				SYS_SETFLAG(SYS_FLAG_BOARD_RENUM);
			else
				SYS_UNSETFLAG(SYS_FLAG_BOARD_RENUM);
			break;


			case OPT_MAX_SUBJECT_LEN:
			if (sl.wcnt > 2) goto EXTRA;

			if (!is_integer(sl.word[1]) ||
			    (max_subject_len=atoi(sl.word[1])) < 1 ||
			    max_subject_len > 255) {
				log(0,"ERROR: Invalid subject length '%s' on line %d. Range is 1 to 255.\n",sl.word[1],linenum);
				exit(1);
				}
			break;


			case OPT_SIGNAL_IGNORE:
			parse_signal_ignore(&sl,linenum);
			break;


			case OPT_MAX_LOGIN_LINES:
			max_login_batch_lines = parse_config_integer(&sl,linenum);
			break;


			case OPT_MAX_LOGOUT_LINES:
			max_logout_batch_lines = parse_config_integer(&sl,linenum);
			break;


			case OPT_MAX_SESSION_LINES:
			max_session_batch_lines = parse_config_integer(&sl,linenum);
			break;


			case OPT_MAX_BATCH_NAME_LEN:
			max_batch_name_len = parse_config_integer(&sl,linenum);
			break;


			case OPT_MAX_INCLUDE_LINES:
			max_include_lines = parse_config_integer(&sl,linenum);
			if (max_include_lines < 1) {
				log(0,"ERROR: Invalid value '%s' on line %d. It must be greater than zero.\n",sl.word[1],linenum);
				exit(1);
				}
			break;


			case OPT_MAX_BROADCAST_CHARS:
			max_broadcast_chars = parse_config_integer(&sl,linenum);
			break;


			case OPT_GO_INVIS_LEVEL:
			go_invis_level = parse_config_level(&sl,linenum);
			break;


			case OPT_PRISONERS_RET_HOME:
			if (parse_config_yn(&sl,linenum))
				SYS_SETFLAG(SYS_FLAG_PRS_RET_HOME);
			else
				SYS_UNSETFLAG(SYS_FLAG_PRS_RET_HOME);
			break;


			case OPT_BIND_INTERFACE:
			if (sl.wcnt > 2) goto EXTRA;
			if (!strcasecmp(sl.word[1],"ALL")) break;
			if ((int)(main_bind_addr.sin_addr.s_addr = inet_addr(sl.word[1])) == -1) {
				log(0,"ERROR: Invalid interface address '%s' on line %d.\n",sl.word[1],linenum);
				exit(1);
				}
			break;


			case OPT_AUTOSAVE_INTERVAL:
			autosave_interval = parse_config_time(&sl,linenum,1);
			break;


			case OPT_LOG_NET_BROADCASTS:
			if (parse_config_yn(&sl,linenum))
				SYS_SETFLAG(SYS_FLAG_LOG_NETBCAST);
			else 
				SYS_UNSETFLAG(SYS_FLAG_LOG_NETBCAST);
			break;


			case OPT_RECV_NET_BCAST_LEVEL:
			recv_net_bcast_level = parse_config_level(&sl,linenum);
			break;


			case OPT_HEXDUMP_PACKETS:
			if (parse_config_yn(&sl,linenum))
				SYS_SETFLAG(SYS_FLAG_HEXDUMP_PACKETS);
			else
				SYS_UNSETFLAG(SYS_FLAG_HEXDUMP_PACKETS);
			break;


			case OPT_STRIP_PRINTCODES:
			if (parse_config_yn(&sl,linenum))
				SYS_SETFLAG(SYS_FLAG_STRIP_PRINTCODES);
			else
				SYS_UNSETFLAG(SYS_FLAG_STRIP_PRINTCODES);
			break;


			case OPT_ALLOW_WHO_AT_LOGIN:
			if (parse_config_yn(&sl,linenum))
				SYS_SETFLAG(SYS_FLAG_ALLOW_WHO_AT_LOGIN);
			else
				SYS_UNSETFLAG(SYS_FLAG_ALLOW_WHO_AT_LOGIN);
			break;


			case OPT_ALLOW_REM_BATCH_RUNS:
			if (parse_config_yn(&sl,linenum))
				SYS_SETFLAG(SYS_FLAG_ALLOW_REM_BATCH_RUNS);
			else
				SYS_UNSETFLAG(SYS_FLAG_ALLOW_REM_BATCH_RUNS);
			break;


			case OPT_ALLOW_NEW_ACCOUNTS:
			if (parse_config_yn(&sl,linenum))
				SYS_SETFLAG(SYS_FLAG_ALLOW_NEW_ACCOUNTS);
			else
				SYS_UNSETFLAG(SYS_FLAG_ALLOW_NEW_ACCOUNTS);
			break;


			case OPT_DEL_DISC_INCOMING:
			if (parse_config_yn(&sl,linenum))
				SYS_SETFLAG(SYS_FLAG_DEL_DISC_INCOMING);
			else
				SYS_UNSETFLAG(SYS_FLAG_DEL_DISC_INCOMING);
			break;


			case OPT_LOG_UX_PACKETS:
			if (parse_config_yn(&sl,linenum))
				SYS_SETFLAG(SYS_FLAG_LOG_UNEXPECTED_PKTS);
			else
				SYS_UNSETFLAG(SYS_FLAG_LOG_UNEXPECTED_PKTS);
			break;


			case OPT_MAX_PACKET_RATE:
			max_packet_rate = parse_config_integer(&sl,linenum);
			break;


			case OPT_INC_ENCRYPT_POL:
			if (sl.wcnt > 2) goto EXTRA;
			if (!strcasecmp(sl.word[1],"NEVER"))
				incoming_encryption_policy = ENCRYPT_NEVER;
			else
			if (!strcasecmp(sl.word[1],"ALWAYS"))
				incoming_encryption_policy = ENCRYPT_ALWAYS;
			else
			if (!strcasecmp(sl.word[1],"EITHER"))
				incoming_encryption_policy = ENCRYPT_EITHER;
			else {
				log(0,"ERROR: Unknown incoming encryption policy '%s' on line %d.\n",sl.word[1],linenum);
				exit(1);
				}
			break;
			

			case OPT_OUT_ENFORCE_ENCRYPTION:
			if (parse_config_yn(&sl,linenum))
				SYS_SETFLAG(SYS_FLAG_OUT_ENF_ENCRYPTION);
			else 
				SYS_UNSETFLAG(SYS_FLAG_OUT_ENF_ENCRYPTION);
			break;


			case OPT_SOFT_MAX_SERVERS:
			// Hard max is 15
			if ((soft_max_servers = parse_config_integer(&sl,linenum)) > 15) {
				log(0,"ERROR: Soft max server limit greater than hard limit of 15 on line %d.\n",linenum);
				exit(1);
				}
			break;

			
			case OPT_INTERNAL_RESOLVE:
			if (parse_config_yn(&sl,linenum))
				SYS_SETFLAG(SYS_FLAG_INTERNAL_RESOLVE);
			else
				SYS_UNSETFLAG(SYS_FLAG_INTERNAL_RESOLVE);
			break;


			case OPT_MAX_USER_DATA_RATE:
			max_local_user_data_rate =  parse_config_integer(&sl,linenum);
			break;


			case OPT_MAX_SERVER_DATA_RATE:
			max_svr_data_rate =  parse_config_integer(&sl,linenum);
			break;


			case OPT_SAVE_NOVICE_ACCOUNTS:
			if (parse_config_yn(&sl,linenum))
				SYS_SETFLAG(SYS_FLAG_SAVE_NOVICE_ACCOUNTS);
			else
				SYS_UNSETFLAG(SYS_FLAG_SAVE_NOVICE_ACCOUNTS);
			break;


			case OPT_REALLY_DELETE_ACCOUNTS:
			if (parse_config_yn(&sl,linenum))
				SYS_SETFLAG(SYS_FLAG_REALLY_DEL_ACCOUNTS);
			else
				SYS_UNSETFLAG(SYS_FLAG_REALLY_DEL_ACCOUNTS);
			break;
			
			case OPT_LOG_GROUPS:
			if (parse_config_yn(&sl,linenum))
				SYS_SETFLAG(SYS_FLAG_LOG_GROUPS);
			else
				SYS_UNSETFLAG(SYS_FLAG_LOG_GROUPS);
			break;


			default:
			log(0,"ERROR: Unknown option '%s' on line %d.\n",sl.word[0],linenum);
			exit(1);
			}
		}

	fgets(line,ARR_SIZE-1,fp);
	linenum++;
	}
fclose(fp);

if (err) {
	log(0,"ERROR: Read failure of configuration file: %s\n",strerror(err));
	exit(1);
	}
if (!server_name[0]) {
	log(0,"ERROR: Server name not specified.");  exit(1);
	}
if (server_timeout < send_ping_interval) {
	puts("ERROR: Server timeout is less than server ping time.");
	exit(1);
	}

if ((int)strlen(default_desc) > max_desc_len) {
	log(0,"ERROR: Max description length is less than default description string length.\n");
	exit(1);
	}

// Print information if not suppressed
if (!suppress_config_info) {
	log(0,"   Server name            : %s\n",server_name);
	log(0,"   Original directory     : %s/\n",original_dir);
	log(0,"   Working directory      : %s/\n",working_dir);
	if (logfile) log(0,"   Log file               : \"%s\"\n",logfile);
	else log(0,"   Log file               : <stdout>");

	log(0,"   Bind interface         : %s\n",
		main_bind_addr.sin_addr.s_addr == INADDR_ANY ?
		"<ALL>" : inet_ntoa(main_bind_addr.sin_addr));

	log(0,"   User port              : %d\n",tcp_port[PORT_TYPE_USER]);
	log(0,"   Server port            : %d\n",tcp_port[PORT_TYPE_SERVER]);
	log(0,"   Server port listening  : %s\n",
		noyes[SYS_FLAGISSET(SYS_FLAG_SVR_LISTEN)]);
	log(0,"   Connect key            : %u\n",local_connect_key);
	if (soft_max_servers)
		log(0,"   Soft max servers       : %d\n",soft_max_servers);
	else
		log(0,"   Soft max servers       : <unlimited>\n");

	if (ansi_terms) {
		strcpy(text,"   Ansi terminals         : ");
		for(i=1;i < ansi_terms->wcnt;++i) 
			sprintf(text,"%s%s,",text,ansi_terms->word[i]);
		if (i > 1) text[strlen(text) - 1] ='\0';
		log(0,"%s\b",text);
		}
	else log(0,"   Ansi terminals         : <none>\n");

	// Ignoring SIGPIPE & SIGWINCH is hardcoded in
	strcpy(text,"   Ignoring signals       : PIPE,WINCH");
	for(i=0;i < NUM_SIGNALS;++i) {
		if (siglist[i].ignoring)
			sprintf(text,"%s,%s",text,siglist[i].name);
		}
	log(0,text);

	log(0,"   Max name length        : %d chars\n",max_name_len);
	log(0,"   Max desc length        : %d chars\n",max_desc_len);
	log(0,"   Max subject length     : %d chars\n",max_subject_len);
	if (max_local_users)
		log(0,"   Max local users        : %d",max_local_users);
	else
		log(0,"   Max local users        : <unlimited>");
	log(0,"   Max remote users       : %d\n",max_remote_users);
	log(0,"   Max user ignore level  : %s\n",user_level[max_user_ign_level]);
	log(0,"   Default description    : \"%s\"\n",default_desc);
	log(0,"   Default password       : \"%s\"\n",default_pwd);
	log(0,"   Min pwd length         : %d chars\n",min_pwd_len);
	log(0,"   Ping interval          : %s\n",time_period(send_ping_interval));
	log(0,"   Autosave interval      : %s\n",time_period(autosave_interval));
	log(0,"   Server timeout         : %s\n",time_period(server_timeout));
	log(0,"   Connect timeout        : %s\n",time_period(connect_timeout));
	log(0,"   Login timeout          : %s\n",
		login_timeout ? time_period(login_timeout) : "<unlimited>");
	log(0,"   Idle timeout           : %s\n",
		idle_timeout ? time_period(idle_timeout) : "<unlimited>");
	log(0,"   Idle timeout ign level : %s\n",
		user_level[idle_timeout_ign_level]);
	log(0,"   Linkdead timeout       : %s\n",time_period(linkdead_timeout));
	log(0,"   Board message expire   : %s\n",
		board_msg_expire ? time_period(board_msg_expire) : "<unlimited>");
	log(0,"   Board renumber         : %s\n",
		noyes[SYS_FLAGISSET(SYS_FLAG_BOARD_RENUM)]);
	log(0,"   Max hop count          : %d\n",max_hop_cnt);
	log(0,"   Max TX errors          : %d\n",max_tx_errors);
	log(0,"   Max RX errors          : %d\n",max_rx_errors);
	if (max_packet_rate)
		log(0,"   Max rx packet rate     : %d per second\n",max_packet_rate);
	else
		log(0,"   Max rx packet rate     : <unlimited>\n");
	if (max_svr_data_rate)
		log(0,"   Max server rx data rate: %d bytes/sec\n",max_svr_data_rate);
	else
		log(0,"   Max server rx data rate: <unlimited>\n");
	if (max_local_user_data_rate)
		log(0,"   Max l. u. rx data rate : %d bytes/sec\n",max_local_user_data_rate);
	else
		log(0,"   Max l. u. rx data rate : <unlimited>\n");
	if (max_profile_chars)
		log(0,"   Max profile chars      : %d\n",max_profile_chars);
	else
		log(0,"   Max profile chars      : <unlimited>");
	if (max_mail_chars)
		log(0,"   Max mail characters    : %d\n",max_mail_chars);
	else
		log(0,"   Max mail characters    : <unlimited>");
	if (max_board_chars) 
		log(0,"   Max board characters   : %d\n",max_board_chars);
	else
		log(0,"   Max board characters   : <unlimited>");
	if (max_broadcast_chars)
		log(0,"   Max broadcast chars    : %d\n",max_broadcast_chars);
	else
		log(0,"   Max broadcast chars    : <unlimited>");
	log(0,"   Max include lines      : %d\n",max_include_lines);
	log(0,"   Max login batch lines  : %d\n",max_login_batch_lines);
	log(0,"   Max logout batch lines : %d\n",max_logout_batch_lines);
	log(0,"   Max session batch lines: %d\n",max_session_batch_lines);
	log(0,"   Max batch name length  : %d\n",max_batch_name_len);
	log(0,"   Num. of review lines   : %d\n",num_review_lines); 
	log(0,"   Loopback users         : %s\n",
		noyes[SYS_FLAGISSET(SYS_FLAG_LOOPBACK_USERS)]);
	log(0,"   Random remote ids      : %s\n",
		noyes[SYS_FLAGISSET(SYS_FLAG_RANDOM_REM_IDS)]);
	log(0,"   Max group name length  : %d\n",max_group_name_len);
	if (max_group_desc_chars) 
		log(0,"   Max group desc chars   : %d\n",max_group_desc_chars);
	else
		log(0,"   Max group desc chars   : <unlimited>");
	log(0,"   Group modify level     : %s\n",user_level[group_modify_level]);
	log(0,"   Group gatecrash level  : %s\n",user_level[group_modify_level]);
	log(0,"   Group invite expire    : %s\n",
		group_invite_expire ?
		time_period(group_invite_expire) : "<unlimited>");
	log(0,"   Log groups             : %s\n",
		noyes[SYS_FLAGISSET(SYS_FLAG_LOG_GROUPS)]);
	log(0,"   Lockout level          : %s\n",user_level[lockout_level]);
	log(0,"   Allow new accounts     : %s\n",
		noyes[SYS_FLAGISSET(SYS_FLAG_ALLOW_NEW_ACCOUNTS)]);
	log(0,"   Save novice accounts   : %s\n",
		noyes[SYS_FLAGISSET(SYS_FLAG_SAVE_NOVICE_ACCOUNTS)]);
	log(0,"   Really delete accounts : %s\n",
		noyes[SYS_FLAGISSET(SYS_FLAG_REALLY_DEL_ACCOUNTS)]);
	log(0,"   Remote user max level  : %s\n",user_level[remote_user_max_level]);
	log(0,"   Go invisible level     : %s\n",user_level[go_invis_level]);
	log(0,"   Prisoners returned home: %s\n",
		noyes[SYS_FLAGISSET(SYS_FLAG_PRS_RET_HOME)]);
	log(0,"   Log net broadcasts     : %s\n",
		noyes[SYS_FLAGISSET(SYS_FLAG_LOG_NETBCAST)]);
	log(0,"   Receive net bcast level: %s\n",user_level[recv_net_bcast_level]);
	log(0,"   Hexdump packets        : %s\n",
		noyes[SYS_FLAGISSET(SYS_FLAG_HEXDUMP_PACKETS)]);
	log(0,"   Strip rsrvd printcodes : %s\n",
		noyes[SYS_FLAGISSET(SYS_FLAG_STRIP_PRINTCODES)]);
	log(0,"   Allow 'who' at login   : %s\n",
		noyes[SYS_FLAGISSET(SYS_FLAG_ALLOW_WHO_AT_LOGIN)]);
	log(0,"   Allow remote batch runs: %s\n",
		noyes[SYS_FLAGISSET(SYS_FLAG_ALLOW_REM_BATCH_RUNS)]);
	log(0,"   Delete discon. incoming: %s\n",
		noyes[SYS_FLAGISSET(SYS_FLAG_DEL_DISC_INCOMING)]);
	log(0,"   Log unexpected packets : %s\n",
		noyes[SYS_FLAGISSET(SYS_FLAG_LOG_UNEXPECTED_PKTS)]);
	log(0,"   Incoming encrpyt policy: %s\n",
		inc_encrypt_polstr[incoming_encryption_policy]);
	log(0,"   Outgoing encryption enf: %s\n",
		noyes[SYS_FLAGISSET(SYS_FLAG_OUT_ENF_ENCRYPTION)]);
	log(0,"   Resolve IP internally  : %s\n",
		noyes[SYS_FLAGISSET(SYS_FLAG_INTERNAL_RESOLVE)]);

	if (lockout_level > remote_user_max_level)
		log(0,"\nNOTE: lockout_level > remote_user_max_level. This will prevent remote users\n      connecting.\n\n");
	}
log(0,"Done.\n");

// Move log file with us if its not a hardcode path else it'll create a new
// log file with the same name in the new directory
if (logfile && !strchr(logfile,'/')) {
	sprintf(path,"%s/%s",working_dir,logfile);
	if (rename(logfile,path) == -1) {
		log(0,"ERROR: Failed to move log file to working directory: %s\n",strerror(errno));
		exit(1);
		}
	}

// Now cd to working dir
if (chdir(working_dir) == -1) {
	log(0,"ERROR: Failed to cd to working directory: %s\n",
		strerror(errno));
	exit(1);
	}
return;


EXTRA:
log(0,"ERROR: Too many values for option '%s' on line %d.\n",sl.word[0],linenum);
exit(1);
}




/*** Value must be integer and > 0 ***/
int parse_config_integer(cl_splitline *sl, int linenum)
{
int val;

if (sl->wcnt > 2) {
	log(0,"ERROR: Too many values for option '%s' on line %d.\n",
		sl->word[0],linenum);
	exit(1);
	}
if (!is_integer(sl->word[1]) || (val = atoi(sl->word[1])) < 0) {
	log(0,"ERROR: Invalid value '%s' on line %d.\n",sl->word[1],linenum);
	exit(1);
	}
return val;
}




/*** Parse a time line in a config file line ***/
int parse_config_time(cl_splitline *sl,int linenum, int gzero)
{
int secs;

if (sl->wcnt > 3) {
	log(0,"ERROR: Too many values for option '%s' on line %d.\n",
		sl->word[0],linenum);
	exit(1);
	}
if ((secs=get_seconds(sl->word[1],sl->wcnt == 3 ? sl->word[2] : NULL)) < 0) {
	log(0,"ERROR: Invalid time or time period on line %d.\n",linenum);
	exit(1);
	}	
if (gzero && !secs) {
	log(0,"ERROR: Time must be greater than zero on line %d.\n",linenum);
	exit(1);
	}
return secs;
}




/*** Parse a level ***/
int parse_config_level(cl_splitline *sl,int linenum)
{
int lev;

if (sl->wcnt > 2) {
	log(0,"ERROR: Too many values for option '%s' on line %d.\n",
		sl->word[0],linenum);
	exit(1);
	}
if ((lev = get_level(sl->word[1])) == -1 || lev < USER_LEVEL_NOVICE) {
	log(0,"ERROR: Invalid user level on line %d.\n",linenum);
	exit(1);
	}
return lev;
}




/*** Parse the remote server lines ***/
void parse_remote_server_line(cl_splitline *sl,int linenum)
{
cl_server *svr;
int svr_port,loc_port;
uint32_t conkey;
u_char enclev;

svr_port = SERVER_PORT;
loc_port = 0;
conkey = 0;
enclev = 0;

switch(sl->wcnt) {
	case 2:
	log(0,"ERROR: Missing ip address for server '%s' on line %d.\n",sl->word[1],linenum);
	exit(1);

	case 3:
	/* Remote address, do nothing with it here */
	break;

	case 7:
	if (!strcasecmp(sl->word[6],"encrypt")) enclev = MAX_ENCRYPTION_LEVEL;
	// Fall through
		
	case 6:
	// Check connect key 
	if (sl->word[5][0] &&
	    (!is_integer(sl->word[5]) || (conkey = atoi(sl->word[5])) < 0)) {
		log(0,"ERROR: Invalid connect key '%s' on line %d.\n",
			sl->word[5],linenum);
		exit(1);
		}
	// Fall through

	case 5:
	// Check local port 
	if (sl->word[4][0] &&
	    (!is_integer(sl->word[4]) ||
	    (loc_port = atoi(sl->word[4])) < 1 || loc_port > 65535)) {
		log(0,"ERROR: Invalid local port number '%s' on line %d. Range is 1 to 65535.\n",sl->word[4],linenum);
		exit(1);
		}
	// Fall through

	case 4:
	// Check remote port 
	if (sl->word[3][0] &&
	    (!is_integer(sl->word[3]) ||
	    (svr_port = atoi(sl->word[3])) < 1 || svr_port > 65535)) {
		log(0,"ERROR: Invalid remote port number '%s' on line %d. Range is 1 to 65535.\n",sl->word[3],linenum);
		exit(1);
		}
	}

if ((svr=new cl_server(
	sl->word[1],
	sl->word[2],
	(uint16_t)svr_port,
	(uint16_t)loc_port,conkey,enclev,NULL))->create_error != OK) {
	log(1,"ERROR: Unable to create new server object on line %d: %s\n",
		linenum,err_string[svr->create_error]);
	exit(1);
	}
}




/*** Parse yes or no on config line ***/
int parse_config_yn(cl_splitline *sl,int linenum)
{
if (sl->wcnt > 2) {
	log(0,"ERROR: Too many values for option '%s' on line %d.\n",
		sl->word[0],linenum);
	exit(1);
	}
if (!strcasecmp(sl->word[1],"NO")) return 0;
else 
if (!strcasecmp(sl->word[1],"YES")) return 1;

log(0,"ERROR: Value must be YES or NO on line %d.\n",linenum);
exit(1);
return 0; // Stops some compilers whinging
}




/*** Parse list of signals to ignore ***/
void parse_signal_ignore(cl_splitline *sl, int linenum)
{
int i,j;

for(i=1;i < sl->wcnt;++i) {
	for(j=0;j < NUM_SIGNALS;++j) 
		if (!strcasecmp(sl->word[i],siglist[j].name)) break;
	if (j == NUM_SIGNALS) {
		log(0,"ERROR: Invalid signal name '%s' on line %d.\n",
			sl->word[i],linenum);
		exit(1);
		}
	siglist[j].ignoring = 1;
	}
SYS_SETFLAG(SYS_FLAG_IGNORING_SIGS);
}




/*** Load banned user & sites list ***/
void load_bans()
{
cl_splitline sl(1);
FILE *fp;
int linenum,ret,ban_level;
int ucnt,scnt;
char line[ARR_SIZE];
char path[MAXPATHLEN];

log(0,"\nLoading bans...");
sprintf(path,"%s/%s",ETC_DIR,BAN_FILE);
if (!(fp=fopen(path,"r"))) {
	log(0,"   No bans found.\nDone.\n");  return;
	}

linenum=1;
fgets(line,ARR_SIZE-1,fp);

for(ucnt=scnt=0;!feof(fp);) {
	if ((ret=sl.parse(line)) != OK) {
		log(0,"ERROR: %s on line %d.\n",err_string[ret],linenum);
		exit(1);
		}
	if (sl.wcnt && sl.word[0][0] != '#') {
		if (sl.wcnt < 3) {
			log(0,"ERROR: Missing arguments on line %d.\n",
				linenum);
			exit(1);
			}
		if ((ban_level = get_level(sl.word[1])) == -1) {
			log(0,"ERROR: Invalid level on line %d.\n",linenum);
			exit(1);
		}
		if (!strcasecmp(sl.word[0],"user ban")) {
			ret=load_user_ban(ban_level,&sl);
			ucnt++;
			}
		else
		if (!strcasecmp(sl.word[0],"site ban")) {
			ret=load_site_ban(ban_level,&sl);
			scnt++;
			}
		else {
			log(0,"ERROR: Invalid option on line %d.\n",linenum);
			exit(1);
			}
		if (ret != OK) {
			log(0,"ERROR: Cannot set ban on line %d: %s\n",
				linenum,err_string[ret]);
			exit(1);
			}
		}
	fgets(line,ARR_SIZE-1,fp);
	linenum++;
	}
fclose(fp);
if (ucnt || scnt)
	log(0,"   %d user bans, %d site bans loaded.\nDone.\n\n",ucnt,scnt);
else log(0,"   Empty file.\nDone.\n");
}




/*** Set user ban ***/
int load_user_ban(int ban_level, cl_splitline *sl)
{
st_user_ban *ub,*ub2;
uint16_t uid,port;
int addr;

if (!(uid = idstr_to_id(sl->word[2]))) return ERR_INVALID_VALUE;

ub = new st_user_ban;
ub->uid = uid;
ub->level = ban_level;
ub->user = NULL;
bzero(&ub->home_addr,sizeof(sockaddr_in));

switch(sl->wcnt) {
	case 3:
	FOR_ALL_USER_BANS(ub2) {
		if (ub2->utype == USER_TYPE_LOCAL && ub2->uid == uid) 
			return ERR_ALREADY_BANNED;
		}
	ub->utype = USER_TYPE_LOCAL;

	log(0,"   Local user : Id = %04X, Ban level = %s\n",
		uid,user_level[ban_level]);
	break;

	case 5:
	if ((addr = inet_addr(sl->word[3])) == -1 ||
	    !(port = htons((uint16_t)atoi(sl->word[4]))))
		return ERR_INVALID_VALUE;

	FOR_ALL_USER_BANS(ub2) {
		if (ub2->utype == USER_TYPE_REMOTE &&
		    ub2->uid == uid &&
		    ub2->home_addr.sin_addr.s_addr == (uint32_t)addr &&
		    ub2->home_addr.sin_port == port)
			return ERR_ALREADY_BANNED;
		}

	ub->utype = USER_TYPE_REMOTE;
	ub->home_addr.sin_addr.s_addr = (uint32_t)addr;
	ub->home_addr.sin_port = port;

	log(0,"   Remote user: Id = %04X, Ban level = %s, IP = %s:%s\n",
		uid,user_level[ban_level],sl->word[3],sl->word[4]);
	break;


	default: return ERR_INVALID_VALUE;
	}
add_list_item(first_user_ban,last_user_ban,ub);	
return OK;
}




/*** Ban a site (for user logon and server connect) ***/
int load_site_ban(int ban_level, cl_splitline *sl)
{
st_site_ban *sb,*sb2;
int addr;

if (sl->wcnt != 4 ||
    (addr = inet_addr(sl->word[2])) == -1) return ERR_INVALID_VALUE;

sb = new st_site_ban;
if (!strcasecmp(sl->word[3],"USER")) sb->type = SITEBAN_TYPE_USER;
else
if (!strcasecmp(sl->word[3],"SERVER")) sb->type = SITEBAN_TYPE_SERVER;
else
if (!strcasecmp(sl->word[3],"ALL")) sb->type = SITEBAN_TYPE_ALL;
else
return ERR_INVALID_VALUE;

FOR_ALL_SITE_BANS(sb2) {
	if (sb2->addr.sin_addr.s_addr == (uint32_t)addr &&
	    (sb2->type == sb->type || 
	     sb2->type == SITEBAN_TYPE_ALL ||
	     sb->type == SITEBAN_TYPE_ALL)) return ERR_ALREADY_BANNED;
	}
sb->level = ban_level;
bzero(&sb->addr,sizeof(sockaddr_in));
sb->addr.sin_addr.s_addr = (uint32_t)addr;
add_list_item(first_site_ban,last_site_ban,sb);

log(0,"   Site ban   : IP = %s, Type = %s, Ban level = %s\n",
	sl->word[2],siteban_type[sb->type],user_level[ban_level]);
return OK;
}




/*** Create system groups ***/
void create_system_groups()
{
system_group_count = 0;
public_group_count = 0;
user_group_count = 0;

gone_remote_group = new cl_group(
		(uint16_t)GONE_REMOTE_GROUP_ID,
		GONE_REMOTE_GROUP_NAME,GROUP_TYPE_SYSTEM,NULL);

remotes_home_group = new cl_group(
		(uint16_t)REMOTES_HOME_GROUP_ID,
		REMOTES_HOME_GROUP_NAME,GROUP_TYPE_SYSTEM,NULL);

prison_group = new cl_group (
		(uint16_t)PRISON_GROUP_ID,
		PRISON_GROUP_NAME,GROUP_TYPE_SYSTEM,NULL);
}




/*** Scan the public groups directory and load them in ***/
void load_public_groups()
{
DIR *dir;
struct dirent *ds;
cl_group *grp;
uint16_t gid;

log(0,"\nLoading public groups...");
if (!(dir=opendir(PUB_GROUP_DIR))) {
	log(0,"WARNING: Unable to scan public groups directory.");
	return;
	}

// Loop through directory entries. Only process subdirectories whos name
// is a valid id and is < MIN_LOCAL_USER_ID.
while((ds=readdir(dir))) {
	if ((gid = idstr_to_id(ds->d_name)) && gid < MIN_LOCAL_USER_ID) {
		if ((grp = new cl_group(gid,NULL))->error != OK) {
			log(0,"   ERROR: Can't create group %04X: %s\n",
				gid,err_string[grp->error]);
			delete grp;
			}
		else log(0,"   Loaded: %04X, %s\n",gid,grp->name);
		}
	}
closedir(dir);
log(0,"Done.\n");
}




/*** Create the events & connect threads. We don't spawn the connect thread
     in the server constructor as this could cause problems when the program
     is still booting. ***/
void spawn_all_threads()
{
cl_server *svr;
pthread_t thrd;

// Add main thread (this thread) to threads list just for info purposes
add_thread_entry(THREAD_TYPE_MAIN,pthread_self());

// Create events thread
if (create_thread(THREAD_TYPE_EVENTS,&thrd,do_events,NULL) == -1) {
	perror("ERROR: pthread()");
	exit(1);
	}

// Spawn server connect threads
if (first_server) {
	log(0,"\nSpawning connect threads...\n");
	FOR_ALL_SERVERS(svr) svr->spawn_thread(THREAD_TYPE_CONNECT);
	}
log(0,"\nBoot complete on: %s\n",ctime(&server_time));
booting = 0;

// Dissociate from tty if running as daemon. Can't do this earlier or we'll
// lose boot/error messages
if (be_daemon) dissociate_from_tty();
}




/*** Self explanatory ***/
void mainloop()
{
struct timeval tv;
int selerr;

selerr=0;

while(1) {
	pthread_mutex_lock(&events_mutex);
	setup_readmask();
	pthread_mutex_unlock(&events_mutex);

	/* Have timeout for select because server connect threads may connect
	   while we're waiting here but the readmask won't be set up on their
	   sockets. This timeout ensures we'll always see them eventually. */
	tv.tv_sec = HEARTBEAT;
	tv.tv_usec = 0;
	switch(select(FD_SETSIZE,&readmask,0,0,&tv)) {
		case -1:
		if (++selerr == MAX_SELECT_ERRORS) {
			log(1,"\nPANIC: select() function keeps erroring.\n");
			exit(1);
			}

		case 0: 
		// Timeout
		selerr = 0;
		continue;
		}

	// Deal with the user input. Set mutex so events can't run while
	// we're doing this
	pthread_mutex_lock(&events_mutex);
	get_input();
	pthread_mutex_unlock(&events_mutex);

	selerr=0;
	}
}




/*** Set up the select readmask ***/
void setup_readmask()
{
cl_user *u;
cl_server *svr;
cl_local_user *lu;

FD_ZERO(&readmask);

FOR_ALL_USERS(u) {
	lu = (cl_local_user *)u;
	if (u->type != USER_TYPE_REMOTE &&
	    !(u->flags & USER_FLAG_LINKDEAD) && lu->sock != -1) 
		FD_SET(lu->sock,&readmask);
	}

FOR_ALL_SERVERS(svr) {
	// SERVER_STAGE_CONNECTING is dealt with in a connect thread
	if (svr->stage == SERVER_STAGE_INCOMING ||
	    svr->stage == SERVER_STAGE_CONNECTED) 
		FD_SET(svr->sock,&readmask);
	}

FD_SET(listen_sock[PORT_TYPE_USER],&readmask);
if (SYS_FLAGISSET(SYS_FLAG_SVR_LISTEN))
	FD_SET(listen_sock[PORT_TYPE_SERVER],&readmask);
}




/*** Get input from users/servers ***/
void get_input()
{
cl_user *u,*nu;
cl_local_user *lu;
cl_server *svr;

if (FD_ISSET(listen_sock[PORT_TYPE_USER],&readmask)) {
	lu = new cl_local_user(0);
	if (lu->error != OK) {
		delete lu;  return;
		}
	// Can't throw exception in constructor so check here
	if (site_is_banned(SITEBAN_TYPE_USER,lu->ip_addr.sin_addr.s_addr)) {
		log(1,"BANNED site, disconnecting...\n");
		// Can't put colour in message 'cos telopt not done
		lu->uprintf("*** CONNECTIONS FROM YOUR SITE ARE NOT ALLOWED ***\n\n");
		lu->disconnect();
		}
	}
if (SYS_FLAGISSET(SYS_FLAG_SVR_LISTEN) &&
    FD_ISSET(listen_sock[PORT_TYPE_SERVER],&readmask)) new cl_server;

// Go through users
for (u=first_user;u;u=nu) {
	lu = (cl_local_user *)u;
	// sock = -1 if linkdead. More efficient than checking flag.
	if (lu->type != USER_TYPE_REMOTE &&
	    lu->sock != -1 && FD_ISSET(lu->sock,&readmask)) {
		try {
			lu->uread();

			/* We set this here because the user could have killed
			   or banned user(s) who are the next one in the linked
			   list and thus would render the pointer invalid if
			   already set. */
			nu=u->next; 
			}
		catch(enum user_stages stg) {
			if (stg == USER_STAGE_DISCONNECT) {
				nu=u->next;
				lu->disconnect();
				}
			else 
			log(1,"INTERNAL ERROR: Caught unexpected user_stage %d in get_input()",stg);
			}
		}
	else nu=u->next;
	}

// Go through servers.
FOR_ALL_SERVERS(svr) {
	// SERVER_STAGE_CONNECTING is dealt with in a connect thread
	if ((svr->stage == SERVER_STAGE_INCOMING ||
	     svr->stage == SERVER_STAGE_CONNECTED) &&
	    FD_ISSET(svr->sock,&readmask)) svr->sread();
	}
}




/*** Signal handler for signals in globals list ***/
void signal_handler(int signum, siginfo_t *siginfo, void *notused)
{
#ifndef BSD
struct passwd *pwd;
#endif
char *astr;
int i;

for(i=0;i < NUM_SIGNALS;++i) {
	if (siglist[i].num == signum) {
		/* If keyboard signal (how does the shell send its signals??)
		   or alarm signal then siginfo ptr not set in Solaris and 
		   si_pid not set in Linux. The less said about BSD the better
		   as it doesn't set anything ever it seems. */
#ifdef BSD
		sprintf(text,"Received signal %d (SIG%s)",signum,siglist[i].name);
#else
		if (siginfo && siginfo->si_pid) {
			pwd=getpwuid(siginfo->si_uid);
			sprintf(text,"Received signal %d (SIG%s) from pid %d (unix uid %d (%s))",
				signum,
				siglist[i].name,
				siginfo->si_pid,
				siginfo->si_uid,
				pwd->pw_name);
			}
		else
		sprintf(text,"Received signal %d (SIG%s) from keyboard",signum,siglist[i].name);
#endif

		if (siglist[i].ignoring) {
			// Reset handler and return
			log(1,"%s but ignoring.\n",text);
			allprintf(MSG_SYSTEM,USER_LEVEL_ADMIN,NULL,"%s but ignoring.\n",text);
			sigaction(signum,&sigact,NULL);	
			return;
			}

		// Reboot if ^C else shutdown.
		shutdown_time = server_time;
		shutdown_type = siglist[i].num == SIGINT ?
		                SHUTDOWN_REBOOT : SHUTDOWN_STOP;

		astr = (siglist[i].num == SIGINT ?
		       (char *)"reboot" : (char *)"shutdown");
		log(1,"%s and initiating %s...\n",text,astr);
		allprintf(MSG_SYSTEM,USER_LEVEL_ADMIN,NULL,"%s and initiating %s...\n",text,astr);

		// Ignore all signals now
		for(i=0;i < NUM_SIGNALS;++i) signal(siglist[i].num,SIG_IGN);
		return;
		}
	}
log(1,"WARNING: Caught unhandled signal %d.\n",signum);
}