/*
 *	popd	    Written by Katie Stevens
 *			       dkstevens@ucdavis.edu
 *			       Computing Services
 *			       University of California, Davis
 *
 *      (C) Copyright 1991 Regents of the University of California
 *
 *      Permission to use, copy, modify, and distribute this program
 *      for any purpose and without fee is hereby granted, provided
 *      that this copyright and permission notice appear on all copies
 *      and supporting documentation, the name of University of California
 *      not be used in advertising or publicity pertaining to distribution
 *      of the program without specific prior permission, and notice be
 *      given in supporting documentation that copying and distribution is
 *      by permission of the University of California.
 *      The University of California makes no representations about
 *      the suitability of this software for any purpose.  It is provided
 *      "as is" without express or implied warranty.
 */
/*
 *
 *	MAIN.C	--  this C program file contains the mainline and
 *		    associated routines for the IP/TCP/POP2 server.
 *
 *	REVISIONS:
 *			12-87	[ks]	original implementation
 *		1.000	 5-88	[ks]
 *		1.001	 2-89	[ks]
 *
 */

#include "globdefs.h"
#include "faildefs.h"
#include "svrdefs.h"
#include "svrvars.h"

/************************** INITIALIZATION **********************************/

bus_intr()
	/* This routine provides for a graceful exit from a */
	/*   system bus error.				    */
{
    fail(FAIL_SIG_BUS);
}

illegal_intr()
	/* This routine provides for a graceful exit from a */
	/*   illegal instruction interrupt error.	    */
{
    fail(FAIL_SIG_ILLEGAL);
}

iot_intr()
	/* This routine provides for a graceful exit from a */
	/*   IOT interrupt error.	    		    */
{
    fail(FAIL_SIG_IOT);
}

seg_intr()
	/* This routine provides for a graceful exit from a */
	/*   system segmentation error.			    */
{
    fail(FAIL_SIG_SEG);
}

term_intr()
	/* This routine provides for a graceful exit from a */
	/*   software termination signal.		    */
{
    fail(FAIL_SIG_TERM);
}

hang_intr()
	/* This routine provides for a graceful exit from */
	/*   a socket hangup signal.			  */
{
    fail(FAIL_SIG_HANG);
}

urg_intr()
	/* This routine provides for a graceful exit from */
	/*   a socket urgent signal.			  */
{
    fail(FAIL_SIG_URGENT);
}

svr_timeout()
	/* This routine is called if the server times out while    */
	/*   waiting for the next command from the client.         */
	/*   Control is passed to this routine via SIGALRM/alarm() */
{
    fail(FAIL_LOST_CLIENT);
}

initial()
	/* This procedure initializes the IP/TCP/POP2 server. It is */
	/*   called once at program startup. 			    */
{
    char buf[80];				/* Buffer for temp storage */

#ifdef LINUX
#ifdef SIGBUS
    signal(SIGBUS,bus_intr);			/* Bomb out gracefully */
#endif
#endif
    signal(SIGSEGV,seg_intr);
    signal(SIGILL,illegal_intr);
    signal(SIGIOT,iot_intr);
    signal(SIGTERM,term_intr);

    signal(SIGHUP,hang_intr);			/* Handle socket signals */
    signal(SIGURG,urg_intr);

    signal(SIGALRM,svr_timeout);		/* In case client disappears */

    gethostname(buf,80);			/* Get hostname for greetings */
    host_i_am = malloc(strlen(buf) + 1);
    if (host_i_am == NULL) fail(FAIL_OUT_OF_MEMORY);
    strcpy(host_i_am,buf);
}

/***************************** EXIT ROUTINES *********************************/

fail(err_num)
    int err_num;				/* Unique error number */
	/* This routine prints an error message and then bombs out. */
	/*   This routine is only called when a fatal error is      */
	/*   encountered. The current folder is released unless the */
	/*   error number indicates grave disorder.		    */
{
    char *pt;					/* Temp char pointer */

    switch(err_num) {
 case FAIL_CANT_RECV:			/* Fail on read from client */
	fld_release();
	pt = "Failure while reading command from client";
	break;
 case FAIL_CANT_SEND:			/* Fail on send to client */
	fld_release();
	pt = "Failure while writing command to client";
	break;
 case FAIL_FILE_CANT_LOCK:		/* Cant lock output file */
	pt = "Can't place file lock";
	break;
 case FAIL_FILE_CREATE:			/* Cant open file for write */
	pt = "Can't open file for output";
	break;
 case FAIL_FILE_IO_ERROR:		/* File read/write error */
	pt = "File I/O error";
	break;
 case FAIL_FILE_NOT_FOUND:		/* Essential file not found */
	pt = "Can't open file for input";
	break;
 case FAIL_FILE_SETUP:			/* Cant setup for file use */
	pt = "Can't set up for file I/O";
	break;
 case FAIL_LOST_CLIENT:			/* Timeout waiting for client */
	fld_release();
	pt = "POP2 client seems to have disappeared";
	break;
 case FAIL_OUT_OF_MEMORY:		/* Out of allocable memory */
	pt = "Out of available memory!";
	break;
 case FAIL_SIG_BUS:			/* UNIX Bus error signal */
	pt = "Bus error     goodbye!";
	break;
 case FAIL_SIG_HANG:			/* UNIX Hangup signal */
	fld_release();
	pt = "Hangup      goodbye!";
	break;
 case FAIL_SIG_ILLEGAL:			/* UNIX Illegal instruction */
	pt = "Illegal instruction     goodbye!";
	break;
 case FAIL_SIG_IOT:			/* UNIX IOT interrupt signal */
	pt = "IOT interrupt     goodbye!";
	break;
 case FAIL_SIG_SEG:			/* UNIX Segmentation error */
	fld_release();
	pt = "Segmentation error     goodbye!";
	break;
 case FAIL_SIG_TERM:			/* UNIX Termination signal */
	fld_release();
	pt = "Software termination signal     goodbye!";
	break;
 case FAIL_SIG_URGENT:			/* UNIX Urgent socket signal */
	fld_release();
	pt = "Urgent socket signal     goodbye!";
	break;
 default:				/* Shouldn't happen, check anyway */
	pt = "Unknown error";
	break;
    }

					/* Display an error msg */
    fprintf(stderr,"- POP2 Server Fatal Error: %s\n",pt);
#ifdef DEBUG
					/* Add error to logfile */
    fprintf(log_fp,"POP2 Server Fatal Error: %s\n",pt);
    fclose(log_fp);
#endif

    exit(err_num);			/* Exit with error value */
}

/********************** SERVER IP/TCP/POP2 DATA SEND *************************/

svr_data_out(pop2_str)
    char *pop2_str;
	/* This routine sends a buffer containing message text to the */
	/*   client. The buffer to send is passed in as pop2_str.     */
{


#ifdef ALONE	/* POP2 server running independent of inetd */

    int num_sent;			/* Temp integer storage */

					/* Send data buffer to client */
    num_sent = send(s_active, pop2_str, strlen(pop2_str), 0);
    if (num_sent == UNDEFINED) {	/* Socket send ok? */
	fail(FAIL_CANT_SEND);		/* No, fail when can't send */
#ifdef DEBUG
    } else {
	fprintf(log_fp,"(%d) %d bytes sent to client\n",s_active,num_sent);
	fflush(log_fp);
#endif
    }


#else	/* POP2 server called by inetd */

    alarm(SVR_SEND_TIMEOUT);		/* Setup in case cant send */
    fputs(pop2_str,stdout);		/* Send data buffer to client */
    fflush(stdout);			/* Make sure it gets out */
    alarm(0);				/* Data got out */
    if (ferror(stdout)) fail(FAIL_CANT_SEND);
#ifdef DEBUG
    fprintf(log_fp,"svr_data_out: %d bytes sent\n",strlen(pop2_str));
    fflush(log_fp);
#endif


#endif
}

/******************************* MAINLINE ************************************/

main()
	/* This routine is the mainline procedure for the POP2 server. */
{
    int len;				/* Temp integer storage */
    int num_recvd,num_sent;		/* Temp byte counters */
    struct sockaddr_in svr_name;	/* Socket struct identifying server */
    struct sockaddr_in cli_name;	/* Socket struct identifying client */

#ifdef DEBUG
    log_fp = fopen(LOGFILE,"w");
    fprintf(log_fp,"\nPOP2 Server   Version %s      %s\n",VERSION,REVDATE);
    fflush(log_fp);
#endif

    initial();				/* Initialize the POP2 server system */



#ifdef ALONE	/* POP2 server running independent of inetd; handle */
		/*   all IP/TCP/socket manipulations, get commands  */
		/*   from client via socket recv(), send replies to */
		/*   client via socket send().			    */

	/* Get the well known socket for the POP2 server */
					/* Get a socket descriptor */
    s_pop2_svr = socket(AF_INET,SOCK_STREAM,0);
    if (s_pop2_svr == UNDEFINED) {	/* Make sure call was successfull */
	fail(FAIL_CANT_RECV);
#ifdef DEBUG
    } else {
	fprintf(log_fp,"socket() call successful: %x\n",s_pop2_svr);
	fflush(log_fp);
#endif
    }

					/* Name the socket using wildcards */
    svr_name.sin_family = AF_INET;		/* This is an Internet socket */
    svr_name.sin_addr.s_addr = INADDR_ANY;	/* Accept any caller */
    /* svr_name.sin_port = htons(109); */	/* Use well known port number */
    svr_name.sin_port = 0;			/* Let UNIX assign port num */
					/* Bind name to socket descriptor */
    if (bind(s_pop2_svr,&svr_name,sizeof(svr_name)) == UNDEFINED) {
	fail(FAIL_CANT_RECV);		/* Fail if bind unsuccessfull */
#ifdef DEBUG
    } else {
	fprintf(log_fp,"bind() call successful\n");
	fflush(log_fp);
#endif
    }
    
    /* Report which arbitrary port number was assigned; once real */
    /*   POP2 port number may be grabbed, leave only as debug msg */
/* #ifdef DEBUG */
					/* Report result of name bind */
    len = sizeof(svr_name);
    if (getsockname(s_pop2_svr,&svr_name,&len) == UNDEFINED) {
	fail(FAIL_CANT_RECV);		/* Fail if can't report */
    } else {
	printf("POP2 server has port %d\n",ntohs(svr_name.sin_port));
	/* fprintf(log_fp,"POP2 server has port %d\n",ntohs(svr_name.sin_port)); */
	fflush(log_fp);
    }
/* #endif */




	/* Wait for a POP2 client to call this address and port */
					/* Tell socket to wait for a client */
					/* Allow only 5 simultaneous requests */
    if (listen(s_pop2_svr,5) == UNDEFINED) {
	fail(FAIL_CANT_RECV);		/* Fail if can't listen for a client */
#ifdef DEBUG
    } else {
	fprintf(log_fp,"listen() call successful\n");
	fflush(log_fp);
#endif
    }



	/* Accept and process clients one at a time. If more than one */
	/*   client attempts connection at one time, up to 5 clients  */
	/*   may be queued for service.				      */
    for ( ; ; ) {
					/* Accept the next client that calls */
	s_active = accept(s_pop2_svr,cli_name,&len);
	if (s_active == UNDEFINED) {	/* Valid socket descriptor returned? */
	    fail(FAIL_CANT_RECV);	/* No, fail on bad accept */
#ifdef DEBUG
	} else {
	    fprintf( log_fp,
		    "accept() call successful return active: %d\n",s_active);
	    fflush(log_fp);
#endif
	}

	    /* Fork off an independent process to handle this client;  */
	    /*   allow this process to loop and accept another client. */

	if (fork() == 0) {		/* Am I the child process? */
					/* Yes, perform POP2 server */

					/* Tell server state machine a */
	    strcpy(from_cli_buf,"open\r\n");	/* client has called   */

	        /* Loop here processing client commands until state */
	        /*   machine calls for exit.			    */
	    for ( ; ; ) {

					/* Take action on clients command */
	        svr_state = servers_turn(from_cli_buf);

					/* Send response back to client */
	        num_sent = send(s_active,from_svr_buf,strlen(from_svr_buf),0);
	        if (num_sent == UNDEFINED) {	/* Socket send ok? */
		    fail(FAIL_CANT_SEND);	/* No, fail when can't send */
#ifdef DEBUG
	        } else {
		    fprintf( log_fp,
			    "(%d) %d bytes sent to client\n",s_active,num_sent);
		    fflush(log_fp);
#endif
	        }
					/* Break out of loop when server done */
	        if (svr_state == SVR_DONE_STATE) break;

					/* Blank buffer for client commands */
	        bzero(from_cli_buf,sizeof(from_cli_buf));


					/* Give up if client takes too long */
	        alarm(SVR_TIMEOUT_BASE);

					/* Wait for next command from client */
	        num_recvd = recv(s_active,from_cli_buf,sizeof(from_cli_buf),0);
	        if (num_recvd == UNDEFINED) {	/* Socket recv ok? */
		    fail(FAIL_CANT_RECV);	/* No, fail when can't recv */
#ifdef DEBUG
	        } else {
		    fprintf( log_fp,
			    "(%d) %d bytes received from client\n",
			     s_active,num_recvd);
		    fprintf(log_fp,"(%d) CLI: %s",s_active,from_cli_buf);
		    fflush(log_fp);
#endif
	        }

	        alarm(0);		/* Client sent command, reset timer */
	    }
	    fld_release();			/* Close folder, if open */

	        /* Server session with one client is done. Close the */
	        /*   connection with this client.                    */
	    if (close(s_active) == UNDEFINED) {	/* Socket close ok? */
	        fail(FAIL_CANT_SEND);		/* No, fail when can't close */
#ifdef DEBUG
	    } else {
	        fprintf( log_fp,
			"(%d) close(s_active) call successful\n",s_active);
		fflush(log_fp);
#endif
	    }

	    exit(EXIT_NORMAL);		/* This invocation of server is done */
	}

					/* No, I am the parent process */

	svr_state = SVR_LSTN_STATE;	/* Prepare for next client */
	/* Loop back and wait for next client */
    }

	/* Actually, never get to here */
	/* Server no longer accepting client connections. Close */
	/*   the server socket.					*/
/*
    if (close(s_pop2_svr) == UNDEFINED) {
	fail(FAIL_CANT_SEND);
#ifdef DEBUG
    } else {
	fprintf(log_fp,"close(s_pop2_svr) call successful\n");
	fflush(log_fp);
#endif
    }

#ifdef DEBUG
    fprintf(log_fp,"\nEnd of program\n");
    fclose(log_fp);
#endif
    exit(EXIT_NORMAL);
 */



#else	/* POP2 server called by inetd; get commands from client */
	/*   via stdin, send replies to client via stdout.       */

					/* Tell server state machine a */
    strcpy(from_cli_buf,"open\r\n");	/* client has called   */

        /* Loop here processing client commands until state */
        /*   machine calls for exit.			    */
    for ( ; ; ) {
					/* Take action on client command */
	svr_state = servers_turn(from_cli_buf);

	alarm(SVR_SEND_TIMEOUT);	/* Setup in case cant send */
	fputs(from_svr_buf,stdout);	/* Send response back to client */
	fflush(stdout);			/* Make sure it gets out */
	alarm(0);			/* Data got out */
	if (ferror(stdout)) break;	/* Must be able to send to client */
#ifdef DEBUG
	fprintf(log_fp,"%d bytes sent to client\n",strlen(from_svr_buf));
	fflush(log_fp);
#endif

					/* Break out of loop when server done */
	if (svr_state == SVR_DONE_STATE) break;

					
	alarm(SVR_TIMEOUT_BASE);	/* Make sure we know if client leaves */

					/* Wait for next command from client */
	if (fgetl(from_cli_buf,POP2_BUFSIZ,stdin) == NULL) {
					/* EOF (or error) on input file */
	    break;			/* Server is done */
	}
#ifdef DEBUG
	fprintf(log_fp,"%d bytes received from client\n",strlen(from_cli_buf));
	if (strn_nocase(from_cli_buf,"HELO",4) == 0) {
	    fprintf(log_fp,"CLI: HELO\n");
	} else {
	    fprintf(log_fp,"CLI: %s",from_cli_buf);
	}
	fflush(log_fp);
#endif
	
	alarm(0);			/* Client sent command, reset timer */
    }

    fld_release();			/* Release current folder, if any */

#ifdef DEBUG
    fprintf(log_fp,"\nEnd of program\n");
    fclose(log_fp);
#endif
    exit(EXIT_NORMAL);



#endif	/* #ifdef ALONE */

}
