/* main.c - main program for BBS
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif				/* HAVE_CONFIG_H */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <pwd.h>
#include "getopt.h"
#include "mstring.h"
#include "terminal.h"
#include "hook.h"
#include "u2u.h"
#include "ldb.h"
#include "bbs.h"


char *current_user;			 /* current username */
char *uid_user;				 /* UID's username */
char *home_dir;				 /* home directory */
char *proc_title;			 /* process title */
char *logout_reason = 0;		 /* reason for logout */
int proc_title_len;			 /* length of process title */
int use_xtitle = 0;			 /* whether to use xterm title */
int action_fd = -1;			 /* fd to write action to */
time_t start_time;			 /* time the client started */
time_t last_keypress;			 /* time of last keypress */
time_t last_idle_update;		 /* when idle time last updated */
int bbs__external = 0;			 /* 1 if user is external */


void display_version(void);		 /* show package version */
void display_help(void);		 /* show command line help */
int cf_global(void);			 /* load global config */
int cf_local(void);			 /* load local config */
void sh_cont(int);			 /* SIGCONT handler */
void sh_int(int);			 /* SIGINT handler */
void sh_quit(int);			 /* SIGQUIT handler */
void sh_alrm(int);			 /* SIGALRM handler */
void sh_hup(int);			 /* SIGHUP handler */
void sh_term(int);			 /* SIGTERM handler */
void sh_tstp(int);			 /* SIGTSTP handler */
void wipe_environ(void);		 /* wipe environment */
void find_satellite(void);		 /* find a status file */
void free_satellite(void);		 /* free status file */
int bbs_external_login(void);		 /* log in an external */
int read_menu(char *, char *, char *, char *, int);	/* menu viewer */

extern int menuview_exitup;
extern int menuview_abort;

typedef void (*sighandler_t) (int);


/* Process command-line arguments, set option flags, and call mview() for
 * each file named. Directories cannot be viewed.
 */
int main(int argc, char *argv[])
{
	char buf[1024];
	sighandler_t oh;
	struct option long_options[] = {
		{"external", 0, 0, 'e'},    /* user is external */
		{"xterm-title", 0, 0, 'x'}, /* use xterm title */
		{"no-xterm-title", 0, 0, 'X'},	/* don't use xterm title */
		{"no-colour", 0, 0, 'c'},
		{"no-colours", 0, 0, 'c'},
		{"monochrome", 0, 0, 'c'},  /* don't use colour */
		{"scan", 0, 0, 's'},	    /* just scan and exit */
		{"help", 0, 0, '!'},	    /* informational options */
		{"version", 0, 0, 'V'},
		{0, 0, 0, 0}
	};
	char *short_options = "exXcVs";
	int option_index = 0;
	struct passwd *z;
	struct stat sb;
	struct tm *t;
	char tbuf[99];
	char *file;
	time_t curt;
	char *pn;
	char *a;
	int c, bw, scan;

	start_time = time(0);
	last_keypress = start_time;
	last_idle_update = start_time;

	pn = strrchr(argv[0], '/');
	if (pn)
		pn++;
	else
		pn = argv[0];
	pn = strdup(pn);

	if (strlen(pn) > 32) {
		fprintf(stderr, "%s? Strange program name - aborting.\n\r",
			pn);
		return (1);
	}

	ldb_init();

	z = getpwuid(getuid());
	if (!z) {
		fprintf(stderr, "%s: uid %d not known\n\r", pn,
			(int) getuid());
		return (1);
	}

	current_user = strdup(z->pw_name);
	uid_user = strdup(z->pw_name);
	home_dir = strdup(z->pw_dir);

	file = getenv("ACTION_FILE");	    /* file to write current action to */
	if ((file) && (!stat(file, &sb)) && (sb.st_uid == getuid())) {
		action_fd = open(file, O_WRONLY);
	}

	setenv("HOME", home_dir, 1);	    /* force value of $HOME */

	a = getenv("TERM");		    /* use xterm title if in an xterm */
	if ((a) && (!strncmp(a, "xterm", 5)))
		use_xtitle = 1;

	bw = 0;				    /* black-and-white output flag */
	scan = 0;			    /* flag, set if just scanning */

	do {				    /* read options */

		c = getopt_long(argc, argv, short_options, long_options,
				&option_index);
		if (c < 0)
			continue;

		switch (c) {

		case 'e':
			bbs__external = 1;
			break;
		case 'x':
			use_xtitle = 1;
			break;
		case 'X':
			use_xtitle = 0;
			break;
		case 'c':
			mstrtoansi_colours(0);
			bw = 1;
			break;
		case 's':
			scan = 1;
			break;
		case 'V':
			display_version();
			return (0);
			break;
		case '!':
			display_help();
			return (0);
			break;

		default:
			fprintf(stderr,
				"Try `%s --help' for more information.\n\r",
				pn);
			return (1);
			break;
		}

	} while (c != -1);

	if (scan && bbs__external) {
		fprintf(stderr, "%s: cannot scan as an external user.\r\n",
			pn);
		return (1);
	}

	if (bw) {
		setenv("BBS_COLOUR", "0", 1);
		mstrtoansi_colours(0);
	}

	if (scan)
		setenv("BBS_SCANONLY", "1", 1);

#ifdef DEBUG				    /* DEBUG */
	/*
	 * don't exec() if debugging,
	 * because gdb doesn't like it
	 */
#else				/* !DEBUG */
	if (strlen(argv[0]) < 100) {	    /* extend process title */
		memset(buf, '.', sizeof(buf) - 1);
		buf[sizeof(buf) - 4 - strlen(pn)] = 0;
		strcat(buf, "/");
		strcat(buf, pn);
		if ((argv[1]) && (!strcmp(argv[1], " "))) {
			argv[1][0] = 0;
			a = getenv("USE_XTITLE");
			if (a)
				use_xtitle = atoi(a);
			a = getenv("USE_EXTERNAL");
			if (a)
				bbs__external = (a[0] == '1') ? 1 : 0;
			a = getenv("USE_NOCOLOUR");
			if ((a) && (a[0] == '1')) {
				setenv("BBS_COLOUR", "0", 1);
				mstrtoansi_colours(0);
			}
			a = getenv("USE_SCANONLY");
			if ((a) && (a[0] == '1')) {
				setenv("BBS_SCANONLY", "1", 1);
				scan = 1;
			}
		} else {
			setenv("USE_XTITLE", (use_xtitle) ? "1" : "0", 1);
			setenv("USE_EXTERNAL", (bbs__external) ? "1" : "0",
			       1);
			setenv("USE_NOCOLOUR", (bw) ? "1" : "0", 1);
			setenv("USE_SCANONLY", (scan) ? "1" : "0", 1);
			execlp(argv[0], buf, " ", 0);
		}
	} else if ((argv[1]) && (!strcmp(argv[1], " "))) {
		a = getenv("USE_XTITLE");
		if (a)
			use_xtitle = (a[0] == '1') ? 1 : 0;
		a = getenv("USE_EXTERNAL");
		if (a)
			bbs__external = (a[0] == '1') ? 1 : 0;
		a = getenv("USE_NOCOLOUR");
		if ((a) && (a[0] == '1')) {
			setenv("BBS_COLOUR", "0", 1);
			mstrtoansi_colours(0);
		}
		a = getenv("USE_SCANONLY");
		if ((a) && (a[0] == '1')) {
			setenv("BBS_SCANONLY", "1", 1);
			scan = 1;
		}
	}
#endif				/* DEBUG */

	proc_title = argv[0] + strlen(pn) + 1;
	proc_title_len = strlen(argv[0]) - strlen(pn) - 1;

	if (proc_title_len > 0) {
		strcpy(argv[0], pn);
		strcat(argv[0], " ");
	} else {
		proc_title = 0;
		proc_title_len = 0;
	}

	if (cf_global())
		return (1);		    /* load global configuration */
	if (cf_local())
		return (1);		    /* load per-user configuration */

	wipe_environ();			    /* wipe MVIEW_* and MCONV_* */

	if (bbs__external) {		    /* external login */
		if (bbs_external_login())
			return (1);
		if (cf_global())
			return (1);	    /* reload global config */
		if (cf_local())
			return (1);	    /* reload per-user config */
	}

	udb_load();			    /* load user database file */

	if (getenv("BBS_SCANONLY"))
		scan = 1;

	/*
	 * Scan only: just list all keypaths that would be stopped at on a
	 * normal scan.
	 */
	if (scan) {
		char u2ufile[1024];
		struct LDB l;
		struct stat sb;
		int newu2u = 0;

		a = getenv("BBS_BANS");
		if (!a)
			a = "";
		if (strchr(a, 'B') != 0) {  /* banned from BBS */
			printf
			    ("\nYou have been banned from this BBS.\n\n");
			sleep(2);
			return 1;
		}

		setenv("BBS_SCANONLY", "1", 1);

		/*
		 * Check U2U file first.
		 */
		sprintf(u2ufile, "%s/%s", cf_str("u2u"), current_user);
		ldb_read(cf_str("ldb"), u2ufile, &l);
		stat(u2ufile, &sb);

		if (l.last_accessed >= sb.st_mtime) {
			if (!l.at_end)
				newu2u = 1;
		} else
			newu2u = 1;
		if (newu2u) {
			t_abswrite("<> Messages\n");
		}

		/*
		 * Scan from main menu.
		 */
		read_menu(cf_str("main"), cf_str("ldb"), 0, 0, 0);

		return 0;
	}

	a = getenv("BBS_LOGINS");
	if (!a)
		a = "0";

	sprintf(tbuf, "%d", 1 + atoi(a));   /* increment login count */
	setenv("BBS_LOGINS", tbuf, 1);

	udb_save();

	t_init(getenv("TERM"));
	t_echo_off();
	t_canon_off();

	signal(SIGALRM, sh_alrm);	    /* ignore SIGALRM */
	signal(SIGINT, sh_int);		    /* ignore ^C */
	signal(SIGHUP, sh_hup);		    /* deal with SIGHUP */
	signal(SIGTERM, sh_term);	    /* deal with SIGTERM */
	signal(SIGTSTP, sh_tstp);	    /* deal with SIGTSTP */
#ifdef DEBUG
	/* don't ignore ^\ - convenient when debugging */
#else				/* !DEBUG */
	signal(SIGQUIT, sh_quit);	    /* ignore ^\ */
#endif				/* DEBUG */
	oh = signal(SIGCONT, sh_cont);	    /* trap SIGCONT */

	find_satellite();
	u2u_init();

	sprintf(buf, "%s/%s", cf_str("status"), current_user);
	rmdir(buf);

	a = getenv("BBS_BANS");
	if (!a)
		a = "";
	if (strchr(a, 'B') == 0) {	    /* not banned from BBS */

		if (read_menu(cf_str("main"), cf_str("ldb"), 0, 0, 0) == 2) {
			fprintf(stderr,
				"%s: error reading main menu '%s'\n\r", pn,
				cf_str("main"));
		} else {
			t_clear();	    /* clear screen on exit */
			if (logout_reason)
				printf("%s\n", logout_reason);
		}

		bbs_hook(HOOK_LOG_CLIENT, 0, 0);

		bbs_hook(HOOK_SET_ACTION, 0, 0);

		if (use_xtitle)
			write(0, "\033]2;\007", 5);	/* reset xtitle */

	} else {			    /* banned from BBS */

		printf("\nYou have been banned from this BBS.\n\n");
		sleep(2);

	}

	free_satellite();

	t_echo_on();
	t_canon_on();

	curt = time(0);			    /* store logout time */
	t = localtime(&curt);
	strftime(tbuf, sizeof(tbuf) - 1, "%a %d %b %Y %H:%M:%S %p %Z", t);
	setenv("BBS_LOGOUT", tbuf, 1);

	udb_save();			    /* save user database file */
	u2u_fini();

	if (bbs__external) {		    /* remove temporary edit file */
		a = getenv("MVIEW_EDIT_TMP");
		if (a)
			remove(a);
	}

	signal(SIGINT, SIG_DFL);
	signal(SIGQUIT, SIG_DFL);
	signal(SIGCONT, oh);
	signal(SIGHUP, SIG_DFL);
	signal(SIGTERM, SIG_DFL);
	signal(SIGTSTP, SIG_DFL);

	return (0);
}


/* Handle SIGCONT by reinitialising the terminal.
 */
void sh_cont(int s)
{
	t_init(getenv("TERM"));
	t_echo_off();
	t_canon_off();
	t_clearscreen = 1;
	signal(SIGCONT, sh_cont);
}


/* Handle SIGALRM by ignoring it.
 */
void sh_alrm(int s)
{
	signal(SIGALRM, sh_alrm);
}


/* Handle SIGHUP by exiting the BBS.
 */
void sh_hup(int s)
{
	menuview_exitup = 1;
	menuview_abort = 1;
	signal(SIGHUP, sh_hup);
}


/* Handle SIGTERM by exiting the BBS.
 */
void sh_term(int s)
{
	menuview_exitup = 1;
	menuview_abort = 1;
	signal(SIGTERM, sh_term);
}


/* Handle SIGTSTP by setting the current action to "Stopped" and stopping
 * the process.
 */
void sh_tstp(int s)
{
	char *oldaction = 0;
	char *oldstr;
	signal(SIGTSTP, SIG_IGN);
	bbs_hook(HOOK_GET_ACTION, &oldaction, 0);
	oldstr = strdup(oldaction);
	bbs_hook(HOOK_SET_ACTION, "Stopped", 0);
	raise(SIGSTOP);
	bbs_hook(HOOK_SET_ACTION, oldstr, 0);
	free(oldstr);
	signal(SIGTSTP, sh_tstp);
}

/* EOF */
