/*
 *	pop3d		- IP/TCP/POP3 server for UNIX 4.3BSD
 *			  Post Office Protocol - Version 3 (RFC1225)
 *
 *      (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.
 *
 *	Katie Stevens
 *	dkstevens@ucdavis.edu
 * 	Information Technology -- Technology Support Program
 *	University of California, Davis
 *
 **************************************
 *
 *	util.c
 *
 *	REVISIONS:
 *		02-27-90 [ks]	original implementation
 *	1.000	03-04-90 [ks]
 *		08-93	 [ks]	Thanks to Kurt Jordan, Purdue Calumet
 *				for the port to SVR3.2
 *	1.005	06-17-94 [ks]	port to SVR4/ANSIC; fold BSD, SVR3.2, SVR4
 *				into one source ( Thanks to Mark Horstman,
 *				Southwester Bell Telephone for this work )
 *      1.010   06-07-96 [ks]   add separate conditional compilation flag,
 *                              SHADOW_PASS, for shadow password support
 *                              Thanks to Peter Tobias for this suggestion.
 *      1.011   01-21-97 [ks]   -- added code to prevent duplicated
 *                              messages and left-over /tmp files. Thanks
 *                              to Blaise Camp, UC Davis for his patches.
 *	1.012   03-27-97 [ks]	-- correct #ifdef condition for struct spwd
 *                              declaration in verify_user()
 *				 -- add NO_MULTIPLE conditional compilation
 *                              code to disallow multiple simultaneous
 *                              POP3 sessions on a single mailbox.
 *      1.013   07-17-97 [ks]   -- correct #ifdef condition for shadow.h
 *                                 #include; thanks to Daniel Roche.
 *	1.015	02-19-98 [ks]	-- added #include <unistd.h> for consistency.
 *                              Thanks to Blaise Camp for this suggestion.
 */

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <pwd.h>
#include <unistd.h>	/* [1.015] */
					/* [1.013] */
#ifndef NO_SHADOW
#include <shadow.h>
#endif
#include "pop3.h"

/* [1.011] In folder.c */
extern char fld_fname[];

/* [1.012] In main.c */
#include <signal.h>
#ifdef NO_MULTIPLE
extern char lockfile[];
extern int remove_lockfile;
#endif /* NO_MULTIPLE */
#ifdef QMAIL
extern char cli_dir[];
#endif

char flash_buf[SVR_BUFSIZ];

#ifdef DEBUG
extern FILE *logfp;
#include <sys/stat.h>
#include <dirent.h>
#include <sys/types.h>
#include <errno.h>
#include <unistd.h>
#endif

#include <fcntl.h>
#ifdef LOG_SESSIONS
#include <syslog.h>
#if (defined (__hpux__) || defined (__linux__))
#include <sys/io.h>
#else
#include <sys/filio.h>
#endif
#define FACILITY        LOG_LOCAL3
extern long byte_count;
extern int retr_count;
#endif

#if (defined (LOG_SESSIONS) || defined (DEBUG))
extern char cli_user[];
#endif

#ifdef UCD 
extern char new_mbox[64];
#endif

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

/* Verify a usercode/password */
int
verify_user(user,pass)
char *user;
char *pass;
{

	struct passwd *pwd = NULL;
	char *cp = NULL;
#ifndef NO_SHADOW
	struct spwd *shad_pwd = NULL;
	char name[64];
#endif
#ifdef DEBUG
	char logname[45];
#endif

	pwd = getpwnam(user);
	if (pwd == NULL) {
		return -1;
	}
			
	if (pwd->pw_name == NULL) {
	    return (-1);
	}
#ifndef NO_SHADOW
	sprintf (name, "##%s", pwd->pw_name);
	/* Check to see if we're doing a shadow passwd lookup.  If so, make
	   the call and then copy relevant info into the plain passwd struct.
	   Changed from the shadow password compile-time option for dist.
	   at locales where there are mixed domains.
	 */
	if ( (strncmp (name, pwd->pw_passwd, strlen (pwd->pw_passwd)) == 0) ||
		((*(pwd->pw_passwd) == 'x' || *(pwd->pw_passwd) == '*' ||
		*(pwd->pw_passwd) == '!') && pwd->pw_passwd[1] == '\0')) {
	    shad_pwd = getspnam(pwd->pw_name);
	    if (shad_pwd == NULL) return -1;
	    if (shad_pwd->sp_pwdp) {
		pwd->pw_passwd = shad_pwd->sp_pwdp;
	    }
	}
#endif
	cp = crypt(pass,pwd->pw_passwd);
	if (strcmp(cp,pwd->pw_passwd) == 0) {
#ifdef QMAIL
			strncpy(cli_dir, pwd->pw_dir, CLI_DIRSIZ);
#endif /* QMAIL */
#ifdef DEBUG
		lockf(fileno(logfp), F_ULOCK, 0);
		fclose (logfp);
		mkdir("/var/log/popdebug", S_IRWXU | S_IRWXG | S_IRWXO);
		sprintf(logname, "/var/log/popdebug/%s", cli_user);
		logfp = fopen(logname, "a+");
		setbuf(logfp, 0);
		if ((lockf(fileno(logfp), F_TLOCK, 0) != 0) &&
				(errno == EACCES || errno == EAGAIN)) {
		    sprintf(logname, "/var/log/popdebug/%s.1", cli_user);
		    logfp = fopen(logname, "a+");
		    setbuf(logfp, 0);
		    lockf(fileno(logfp), F_LOCK, 0);
		}
		fprintf(logfp, "--- Begin ---\n");
#endif
		return(setuid(pwd->pw_uid));
	} else {
		return(-1);
	}
}

/********************************************************/
/* Read a line of text from a stream. If more than n-1  */
/* characters are read without a line terminator (LF),  */
/* discard characters until line terminator is located. */
/********************************************************/
long
fgetl(buf,n,fp)
char *buf;	/* Buffer for text */
int n;		/* Size of buffer */
FILE *fp;	/* Stream to read from */
{
#ifdef LOG_SESSIONS
	long b_count = 0;
	long b_tmp = 0;
#endif
	if (fgets(buf,n,fp) == NULL)
		return(-1);
#ifdef LOG_SESSIONS
	if (((b_count = strlen(buf)) == (n-1))&&(buf[n-1] != LF_CHAR)) {
#else
	if ((strlen(buf) == (n-1))&&(buf[n-1] != LF_CHAR)) {
#endif
		buf[n-1] = LF_CHAR;
		while (fgets(flash_buf,SVR_BUFSIZ,fp) != NULL) {
#ifdef LOG_SESSIONS
			if ((b_tmp = strlen(flash_buf)) != (SVR_BUFSIZ-1)) {
				b_count += b_tmp;
#else
			if (strlen(flash_buf) != (SVR_BUFSIZ-1)) {
#endif
				break;
			}
			if (flash_buf[SVR_BUFSIZ-1] == LF_CHAR)
				break;
		}
	}
#ifdef LOG_SESSIONS
	return (b_count);
#else
	return(1);
#endif
}

/* Prepare client command for server */
void
cmd_prepare(buf)
char *buf;
{
	char *cp;

	if (buf == NULL)
		return;
	/* Convert command verb to lowercase */
	while (*buf != NULL_CHAR) {
		if (isupper((int)*buf))
			*buf = tolower(*buf);
		else if (isspace((int)*buf))
			break;
		++buf;
	}
	/* Strip trailing whitespace from client command */
	if ((cp = strchr(buf,CR_CHAR)) != NULL) {
		while ((cp != buf)&&(isspace((int)*cp))) --cp;
		if (!isspace((int)*cp)) ++cp;
		*cp = NULL_CHAR;
	}
	if ((cp = strchr(buf,LF_CHAR)) != NULL) {
		while ((cp != buf)&&(isspace((int)*cp))) --cp;
		if (!isspace((int)*cp)) ++cp;
		*cp = NULL_CHAR;
	}
}

/**********************************************/
/* Send an error message and exit POP3 server */
/**********************************************/
void
fail(err, sig)
int err;
int sig;
{
	char *cp;
	int filenum;
#ifdef NO_MULTIPLE
	int unlink_result;
#endif
#ifdef DEBUG
    	struct stat lock_stat; 
	struct dirent *dir_info;
	DIR *dirp;
#endif

	/* While often these are already set, there are also many places
	 * where fail() is called when the signals aren't ignored. 
	 * Ignoring them because we're already on the way out, and don't 
	 * want the process interrupted.  Unfortunately this leaves gaps
	 * where signals could still get through, but I don't want to 
	 * add these three lines in 25 different places until I'm sure there's
	 * a real need.
	 */
        signal(SIGPIPE, (void *)SIG_IGN);
	signal(SIGHUP, (void *)SIG_IGN);
	signal(SIGINT, (void *)SIG_IGN);

/* [1.012] Disallow multiple simultaneous POP3 sessions on single mailbox */
/* Remove the lockfile. */
#ifdef NO_MULTIPLE
#ifdef DEBUG
	    if(stat(lockfile, &lock_stat) == 0) { /* The file exists */
		fprintf(logfp, "%s: u - about to unlink\n",cli_user);
		fflush(logfp);
#endif
                unlink_result = unlink(lockfile);
#ifdef DEBUG
		fprintf(logfp, "%s: u - after unlink\n",cli_user);
		if (unlink_result == 0) {
	    	    fprintf(logfp,"%s: u - unlink lockfile %s ok\n", cli_user, lockfile);

		    if(stat(lockfile, &lock_stat) == 0) {    
			fprintf(logfp,"%s: u - lockfile still exists after unlink\n",
				cli_user);
		    }
		    fflush(logfp);
		} else {
	    	    fprintf(logfp, "%s: u - unlink lockfile %s FAILED\n", 
			    cli_user, lockfile);
		    fflush(logfp);
		}
	    }
	    else {
		fprintf(logfp, "%s: u - No lockfile to unlink\n", cli_user);
		fflush(logfp);
	    }

	    dirp = opendir("/var/tmp");
	    if (dirp) {
	        while ((dir_info = readdir(dirp))) {
		    if (strncmp(dir_info->d_name, "pop_", 4) == 0) {
		        fprintf(logfp,"%s: u - file %s\n",cli_user,dir_info->d_name);
		    }
	        }
		closedir(dirp);
	    }
#endif /* DEBUG */
#endif /* NO_MULTIPLE */

	switch(err) {
	case FAIL_FILE_ERROR:			/* File I/O error */
		cp = "File I/O error";
		break;
	case FAIL_HANGUP:			/* Client hung up on us */
		cp = "Lost connection to client";
		break;
	case FAIL_LOST_CLIENT:			/* Timeout waiting for client */
		cp = "Timeout waiting for command from client";
		break;
	case FAIL_OUT_OF_MEMORY:		/* Failed malloc() */
		cp = "Out of memory!";
		break;
	case FAIL_PROGERR:			/* Fatal program error */
		cp = "Fatal program error!";
		break;
	case FAIL_CONFUSION:			/* Shouldn't happen */
	default:
		cp = "Complete confusion!";
		break;
	}
	if (sig != SIGPIPE) {
	    filenum = fileno(stdout);
	    fcntl(filenum, F_SETFL, O_NONBLOCK);
	    fprintf(stdout,
	       "-ERR POP3 Server Abnormal Shutdown: %s: signal %d\r\n",cp, sig);
	    fflush(stdout);
	}
#ifdef DEBUG
	fprintf(logfp,"%s: -ERR POP3 Server Abnormal Shutdown: %s\r\n",cli_user, cp);
#endif

#ifdef UCD
	if (new_mbox[0] != NULL) {
	    unlink(new_mbox);
	}
#endif

#ifdef LOG_SESSIONS
        /* Log the number of messages retrieved, and how many bytes those
           messages comprise.
        */
	if (byte_count) {
	    openlog( "pop3", LOG_PID, FACILITY );
	    syslog( LOG_INFO | FACILITY, "%s: error-exit, sig %d",cli_user, sig);
	    syslog( LOG_INFO | FACILITY, 
		    "%s: %d %d",cli_user, retr_count, byte_count );
	    closelog();
	}
#endif

        unlink(fld_fname);                      /* [1.011] Remove /tmp file */
#ifdef DEBUG
	fprintf(logfp, "%s: u - exit\n", cli_user);
	lockf(fileno(logfp), F_ULOCK, 0);
	fclose(logfp);
#endif
	exit(err);				/* Exit with error */
}
