/*****************************************************************************
 *
 * File ..................: mbsed/mbsed.c
 * Purpose ...............: MBSE BBS Daemon
 * Last modification date : 18-Apr-1999
 *
 *****************************************************************************
 * Copyright (C) 1997-1999
 *   
 * Michiel Broek		FIDO:		2:2801/16
 * Beekmansbos 10		Internet:	mbroek@ux123.pttnwb.nl
 * 1971 BV IJmuiden
 * the Netherlands
 *
 * This file is part of MBSE BBS.
 *
 * This BBS 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, or (at your option) any
 * later version.
 *
 * MB BBS is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with MB BBS; see the file COPYING.  If not, write to the Free
 * Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *****************************************************************************
 *
 * This program uses the BSD IPC stream sockets mechanism. This is the
 * server program. You need an entry in /etc/services for tcp service
 * "mbse" on port 60180. Yes, this is 1 higher as for ifcico. You don't 
 * need an entry in inetd.conf, because this program is written as a
 * daemon for fast access.
 *
 *****************************************************************************/


#include "libs.h"
#include "logger.h"
#include "status.h"
#include "rdconfig.h"
#include "mbsed.h"
#include "reginfo.h"
#include "crc.h"
#include "diskstat.h"


int ss;				/* connected streams socket descriptor	*/
int ls;				/* listen socket descriptor		*/

struct hostent *hp;		/* pointer to host info for remote host	*/
struct servent *sp;		/* pointer to service information	*/

struct linger linger = {1,1};	/* allow a lingering gracefull close	*/
				/* used when setting SO_LINGER		*/

struct sockaddr_in myaddr_in;	/* for local socket address		*/
struct sockaddr_in peeraddr_in;	/* for peer socket address		*/

char *rbbs=(char *)"";		/* root path				*/
pid_t pgrp;			/* Pids group				*/
int authorized = 0;		/* Set to 1 if authorized		*/
int altime = 600;		/* Alarm time				*/

extern char *authcode;		/* Authorisation code			*/
extern int oserr;		/* Copy of Unix error			*/
extern int logdebug;		/* Debug loglevel			*/
extern int logtrans;		/* Log transactions			*/
extern char *version;		/* Version number			*/
extern char *reldate;		/* Version stage			*/
extern char *copyright;		/* Copyright message			*/


/************************************************************************
 *
 *  Logging procedures.
 */


int userlog(char *param)
{
	char *prname, *prpid, *grade, *msg;
	static char lfn[64];
	static char token[14];

	lfn[0] = '\0';
	strcpy(token, strtok(param, ","));
	strcpy(token, strtok(NULL, ","));
	sprintf(lfn, "%s/log/%s", rbbs, token); 
	prname = strtok(NULL, ",");
	prpid = strtok(NULL, ",");
	grade = strtok(NULL, ",");
	msg = strtok(NULL, ";");
	return ulog(lfn, grade, prname, prpid, msg);
}



/************************************************************************
 *
 * Socket protocol handlers.
 */


void stop_daemon(char *msg)
{
	mbselog((char *)"?", (char *)"Caught %s, shutting down", msg);
	shutdown(ss, 2);
	killpg(pgrp, SIGKILL);
}



void sig_kill(void)
{
	mbselog((char *)"?", (char *)"Caught SIGKILL, exiting");
	stop_daemon((char *)"SIGKILL");
	exit(1);
}



void bus_error(void)
{
	mbselog((char *)"?", (char *)"Caught SIGBUS, dumping core");
	abort();
	exit(1);
}



/* By catching the SIGSEFV and SIGBUS signals the daemon will still
 * be running if a child caught these signals. Oke, the child dies but
 * other clients may have no troubles at all with this daemon.
 */

void seg_fault(void)
{
	mbselog((char *)"?", (char *)"Caught SIGSEGV, dumping core");
	abort();
	exit(1);
}



void just_die(void)
{
	exit(0);
}



void set_signals(void)
{
	signal(SIGBUS, (void (*))bus_error);
	signal(SIGSEGV,(void (*))seg_fault);
	signal(SIGTERM, SIG_IGN);
	signal(SIGKILL,(void (*))sig_kill);
}




/************************************************************************
 *
 * Child server process.
 */


void sig_alarm(void)
{
	mbselog((char *)"?", (char *)"Caught SIGALRM, exiting child process");
	shutdown(ss,2);
	close(ss);
	stat_inc_cerr();
	reg_panic();
	exit(1);
}



/*
 * Process command received from the client.
 */

char *do_cmd(char *in)
{
	static char	obuf[SS_BUFSIZE];
	static char	ibuf[SS_BUFSIZE];
	static char	cmd[4];
	static char	token[SS_BUFSIZE];
	static char	ebuf[19];
	static char	*cnt, var1[16], var2[8];
	int		result;

	strcpy(ibuf, in);
	strncpy(cmd, ibuf, 4);
	token[0] = '\0';
	strcpy(ebuf, "200:1,Syntax error;");

	/*
	 * Split the commandline after the colon so we can give the
	 * options directly to the actual functions. Als set a default
	 * and most used answer.
	 */
	strcpy(token, &ibuf[5]);
	strcpy(obuf, "100:0;");


	/*
	 * The A(counting) commands.
	 */
	if (strncmp(cmd, "AINI", 4) == 0) {
		if (reg_newcon(token) == 0)
			return obuf;
		else {
			stat_inc_serr();
			return ebuf;
		}
	}
	if (strncmp(cmd, "AAUT", 4) == 0) {
		cnt = strtok(token, ",");
		strcpy(var1, strtok(NULL, ","));
		strcpy(var2, strtok(NULL, ";")); 
		sprintf(cnt, "%08lx", str_crc32(strcat(var1, authcode)));
		if (strcmp(var2, cnt) == 0) {
			authorized = 1;
			return obuf; 
		}
		/* If authorisation failed, fall thru and return error */
	}

	/* At this point, we must have set the authorized flag,
	 * If not, we return allways an error.
	 */
	if (authorized == 0) {
		mbselog((char *)"?", (char *)"Client failed autorisation");
		strcpy(obuf, "104:0;");
		return obuf;
	}

	if (strncmp(cmd, "ADOI", 4) == 0) {
		if (reg_doing(token) == 0)
			return obuf;
		else {
			stat_inc_serr();
			return ebuf;
		}
	}
	if (strncmp(cmd, "ATTY", 4) == 0) {
		if (reg_tty(token) == 0)
			return obuf;
		else {
			stat_inc_serr();
			return ebuf;
		}
	}
	if (strncmp(cmd ,"ACLO", 4) == 0) {
		if (reg_closecon(token) == 0) {
			strcpy(obuf, "107:0;");
			return obuf;
		} else {
			stat_inc_serr();
			return ebuf;
		}
	}
	if (strncmp(cmd, "ALOG", 4) == 0) {
		if (userlog(token) != 0) 
			sprintf(obuf, "201:1,%d;", oserr);
		return obuf;
	}
	if (strncmp(cmd, "AUSR", 4) == 0) {
		if (reg_user(token) == 0) 
			return obuf;
		else {
			stat_inc_serr();
			return ebuf;
		}
	}
	if (strncmp(cmd, "ADIS", 4) == 0) {
		if (reg_silent(token) == 0)
			return obuf;
		else {
			stat_inc_serr();
			return ebuf;
		}
	}
	if (strncmp(cmd, "ATIM", 4) == 0) {
		cnt = strtok(token, ",");
		strcpy(var1, strtok(NULL, ";"));
		if (atoi(var1) < 600) 
			return ebuf;
		else {
			altime = atoi(var1);
			mbselog((char *)"+", (char *)"Alarm time set to %d", altime);
			return obuf;
		} 
	}
	if (strncmp(cmd, "ADEF", 4) == 0) {
		altime = 600;
		mbselog((char *)"+", (char *)"Alarm time set to 600");
		return obuf;
	}

	/*
	 * The chat commands
	 */
	if (strncmp(cmd, "CIPM", 4) == 0) {
		return reg_ipm(token);
	}
	if (strncmp(cmd, "CSPM", 4) == 0) {
		if ((result = reg_spm(token))) {
			sprintf(obuf, "100:1,%d;", result);
			return obuf;
		} else
			return obuf;
	}

	/*
	 * The G(lobal) commands.
	 */
	if (strncmp(cmd ,"GNOP", 4) == 0) {
		return obuf;
	}
	if (strncmp(cmd, "GPNG", 4) == 0) {
		sprintf(obuf, "100:%s", token);
		return obuf;
	}
	if (strncmp(cmd, "GVER", 4) == 0) {
		sprintf(obuf, "100:1,Version %s %s;", version, reldate);
		return obuf;
	}
	if (strncmp(cmd, "GSTA", 4) == 0) {
		return stat_status();
	}
	if (strncmp(cmd, "GMON", 4) == 0) {
		return get_reginfo(1);
	}
	if (strncmp(cmd, "GMNE", 4) == 0) {
		return get_reginfo(0);
	}
	if (strncmp(cmd, "GDST", 4) == 0) {
		return get_diskstat();
	}

	/*
	 * The (S)tatus commands.
	 */
	if (strncmp(cmd, "SBBS", 4) == 0) {
		switch(stat_bbs_stat()) {
			case 0:
				break;
			case 1:
				sprintf(obuf, "101:1,The BBS is closed right now!;");
				break;
			case 2:
				sprintf(obuf, "101:1,The BBS is closed for Zone Mail Hour!;");
				break;
		}
		return obuf;
	}
	if (strncmp(cmd, "SZMH", 4) == 0) {
		if (stat_bbs_stat() == 2) 
			sprintf(obuf, "101:0;");
		return obuf;
	}
	if (strncmp(cmd, "SOPE", 4) == 0) {
		stat_set_open(1);
		return obuf;
	}
	if (strncmp(cmd, "SCLO", 4) == 0) {
		stat_set_open(0);
		return obuf;
	}
	if (strncmp(cmd, "SFRE", 4) == 0) {
		return reg_fre();
	}
	if (strncmp(cmd, "SSEQ", 4) == 0) {
		return getseq();
	}

	/*
	 * If we got this far, there must be an error.
	 */
	stat_inc_serr();
	return ebuf;
}



void server_st(void)
{
	int reqcnt = 0;		/* keeps count of nr of requests	*/
	char buf[SS_BUFSIZE];	/* communications buffer		*/
	char *hostname;		/* points to the remote host's name	*/
	int len, lenl;
	int loop = 1;		/* loop processing flag			*/

	/*
	 * Close the listen socket inherited from the daemon
	 */
	close(ls);
	authorized = 0;

	/*
	 * Look up the host information for the remote host 
	 * that we have connected with. Its internet address
	 * was returned by the connect call, in the main
	 * daemon loop above.
	 */
	hp = gethostbyaddr((char *)&peeraddr_in.sin_addr, sizeof(struct in_addr), peeraddr_in.sin_family);
	if (hp == NULL) 
		hostname = inet_ntoa(peeraddr_in.sin_addr);
	else
		hostname = hp->h_name;

	if (logdebug >= 3) 
		mbselog((char *)"-", (char *)"Connect from %s port %u", hostname, ntohs(peeraddr_in.sin_port));

	if (setsockopt(ss, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger)) == -1) {
errout:		mbselog((char *)"?", (char *)"Connection with %s aborted on error\n", hostname);
		stat_inc_cerr();
		exit(1);
	}

	memset((char *)&buf, 0, SS_BUFSIZE);
	stat_inc_clients();
	
	/*
	 * Setup a 10 minutes overall timer in case the client disapears.
	 * The client should keep the connection alive, if it has nothing
	 * to do and wants to keep the connection alive, it should send    
	 * the GNOP command at regular intervals.
	 */
	signal(SIGALRM,(void (*))sig_alarm);
	while (loop) {
		alarm(altime);
		len = 0;
		while ((strchr(buf, ';')) == NULL) { 
			lenl = recv(ss, &buf[len], SS_BUFSIZE-len, 0);
			if (lenl == -1)
				goto errout;
			len += lenl;
		}
		alarm(0);
		reqcnt++;
		if (logtrans != 0)
			mbselog((char *)"$" ,(char *)"< %s", buf);
		strcpy(buf, do_cmd(buf));
		if (logtrans != 0)
			mbselog((char *)"$" ,(char *)"> %s", buf);
		if (strncmp(buf, "107:0;", 6) == 0)
			loop = 0;
		if (send(ss, buf, strlen(buf), 0) != strlen(buf))
			goto errout;
		memset((char *)&buf, 0, SS_BUFSIZE);
	} 
	alarm(0);
	close(ss);
	stat_dec_clients();
	if (logdebug >= 3)
		mbselog((char *)"-", (char *)"Disconnect %s, %d requests", hostname, reqcnt);
}




static int make_sock(const struct sockaddr_in *server)
{
	int s;
	const int one = 1;
	const int keepalive_value = 1;

	s = socket(AF_INET, SOCK_STREAM, 0);
	if (s == -1) {
		perror("socket");
		mbselog((char *)"?", (char *)"$Unable to create socket");
		exit(1);
	}
	
	/* note_cleanups_for_fd(pconf, ls); */

	if ((setsockopt(s, SOL_SOCKET, SO_REUSEADDR,(char *)&one, sizeof(one))) == -1) {
		perror("setsockopt(SO_REUSEADDR)");
		mbselog((char *)"?", (char *)"$Could not set socket option SO_REUSEADDR");
		exit(1);
	}

	if ((setsockopt(s, SOL_SOCKET,SO_KEEPALIVE,(char *)&keepalive_value, sizeof(keepalive_value))) == -1) {
		perror("setsockopt(SO_KEEPALIVE)");
		mbselog((char *)"?", (char *)"$Could not set socket option SO_KEEPALIVE");
		exit(1);
	}

/*	sock_disable_naggle(s); */

	if (bind(s, (struct sockaddr *)server, sizeof(struct sockaddr_in)) == -1) {
		perror("bind");
		mbselog((char *)"?", (char *)"$Unable to bind address %s port %d", inet_ntoa(server->sin_addr), ntohs(server->sin_port)); 
		exit(1);
	}
	if (listen(s, 20) == -1) {
		mbselog((char *)"?", (char *)"$Unable to listen on socket");
		exit(1);
	}
	if (logdebug >= 3)
		mbselog((char *)"-", (char *)"Tcp listen on port %d, address %s", ntohs(server->sin_port), inet_ntoa(server->sin_addr));
	return s;
}



int main(int argc, char *argv[])
{
	int addrlen, frk;
	
	/*
	 * Print copyright notices and setup logging.
	 */
	rbbs = getenv("MBSE_ROOT");
	printf("MBSED: MBSE BBS %s %s Monitor Deamon\n", version, reldate); 
	printf("       Copyright (c) %s\n\n", copyright);

	mbselog((char *)" ", (char *)"MBSE BBS %s %s Monitor Deamon starting", version, reldate);

	readconfig(rbbs);
	status_init(rbbs);
	reg_init(rbbs);

	/*
	 * Clear out address structures 
	 */
	memset((char *)&myaddr_in, 0, sizeof(struct sockaddr_in));
	memset((char *)&peeraddr_in, 0, sizeof(struct sockaddr_in));

	/*
	 * Setup address structure for the listen socket, listen to
	 * the wildcard address, get the service name and create the
	 * listen socket. Then bind the listen address to the socket
	 * and start to listen with a backlog of 5.
	 */
	myaddr_in.sin_family = AF_INET;
	myaddr_in.sin_addr.s_addr = INADDR_ANY;
	sp = getservbyname("mbse", "tcp");
	if (sp == NULL) {
		mbselog((char *)"?", (char *)"Socket 'mbse' not found in /etc/services");
		printf("PANIC: Socket 'mbse' not found in /etc/services\n");
		exit(1);
	}

	myaddr_in.sin_port = sp->s_port;
	ls = make_sock(&myaddr_in);

	set_signals();

	/*
	 * Server initialization is complete. Now we can fork the 
	 * daemon and return to the user. We need to do a setpgrp
	 * so that the daemon will no longer be assosiated with the
         * users control terminal. This is done before the fork, so
         * that the child will not be a process group leader. Otherwise,
	 * if the child were to open a terminal, it would become
	 * associated with that terminal as its control terminal.
	 */
	if ((pgrp = setpgrp()) == -1) {
		mbselog((char *)"?", (char *)"$setpgrp failed");
		exit(1);
	}

	frk = fork();
	switch (frk) {
	case -1:
		mbselog((char *)"?" ,(char *)"$Unable to fork daemon");
		exit(1);
	case 0:
		/*
		 * Starting the deamon child process here.
		 */
		fclose(stdin);
		fclose(stderr); 
		signal(SIGCHLD, SIG_IGN);

		for(;;) {
			/*
			 * Note that addrlen is passed as a pointer
			 * so that the accept call can return the
			 * size of the returned address.
			 */
			addrlen = sizeof(struct sockaddr_in);

			/*
			 * This call will block until a new 
			 * conection arrives. Then it will
			 * return the address of the incoming
			 * peer, and a new socket descriptor, s,
			 * for that connection.
			 */
			ss = accept(ls, (struct sockaddr *)&peeraddr_in, &addrlen);
			if (ss == -1) {
				mbselog((char *)"?", (char *)"$Accept call failed");
				exit(1);
			}
			switch (fork()) {
			case -1:
				mbselog((char *)"?",(char *)"$Child fork failed");
				exit(1);
			case 0:
				server_st();
				exit(0);
			default:
				close(ss);
			} 
		}
	default:
		/*
		 * Here we detach this process and let the child
		 * run the deamon process.
		 */
		if (logdebug >= 2)
			mbselog((char *)"+",(char *)"Starting daemon with pid %d", frk);
		exit(0);
	}
	/* Not reached */
	return 0; /* To satisfy some compilers */
}


