/*
 * Copyright (c) 1999 Ian Freislich
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *      $Id: funcs.c,v 1.38 2003/03/03 12:10:19 ianf Exp $
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <memory.h>
#include <poll.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <syslog.h>
#include <unistd.h>

#ifdef USE_SSL
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
#endif

#include "poputil.h"
#include "private.h"

/* Globals */
static int		 fd_in = -1, fd_out = -1, flags;
static FILE		*socket_in;
static time_t		 timeout = 0;
#ifdef USE_SSL
static int		use_ssl = FALSE;
static char		*ssl_cert = NULL, *ssl_key = NULL;
static SSL_CTX		*ctx = NULL;
static SSL		*ssl = NULL;
static X509		*client_cert = NULL;
static SSL_METHOD	*meth = NULL;
#endif

static void makelowercase(char *);

void *
xmalloc(size_t size) {
	void    *mem;
	if ((mem = malloc(size)) == NULL ) {
		syslog(LOG_NOTICE, "Out of memory");
		exit_error(EX_OSERR, "Out of memory");
	}
	return(mem);
}

void *
xcalloc(size_t number, size_t size) {
	void    *mem;
	if ((mem = calloc(number, size)) == NULL ) {
		syslog(LOG_NOTICE, "Out of memory");
		exit_error(EX_OSERR, "Out of memory");
	}
	return(mem);
}

void *
xrealloc(void *ptr, size_t size) {
	void    *mem;
	if ((mem = realloc(ptr, size)) == NULL ) {
		syslog(LOG_NOTICE, "Out of memory");
		exit_error(EX_OSERR, "Out of memory");
	}
	return(mem);
}

#ifdef USE_SSL
void
ssl_accept(int fd)
{
	if (use_ssl) {
		if ((ssl = SSL_new(ctx)) == NULL) {
			syslog(LOG_NOTICE, "Out of memory");
			exit(EX_OSERR);
		}
		SSL_set_fd(ssl, fd);
		if (SSL_accept(ssl) < 0) {
			syslog(LOG_NOTICE, "Unable to accept SSL connection");
			exit(EX_PROTOCOL);
		}
		if ((client_cert = SSL_get_peer_certificate(ssl)) != NULL) {
			/* Perhaps do some certificate verification in the
			 * future
			 */
			X509_free(client_cert);
		}
	}
}
#endif

size_t
xwrite(const void *buf, size_t nbytes)
{
	size_t len;

#ifdef USE_SSL
	if (use_ssl)
		len = SSL_write(ssl, buf, nbytes);
	else
#endif
		len = write(fd_out, buf, nbytes);
	return(len);
}

size_t
xread(void *buf, size_t nbytes)
{
	size_t	len;

#ifdef USE_SSL
	if (use_ssl)
		len = SSL_read(ssl, buf, nbytes);
	else
#endif
		len = read(fd_in, buf, nbytes);
	return(len);
}

void
makelowercase(char *string)
{
	for (; (*string = tolower(*string)); string++);
}

void
exit_error(int exitcode, char *format, ...)
{
	va_list	pvar;
	char	*output;

	va_start(pvar, format);
	if (vasprintf(&output, format, pvar) < 0) {
		syslog(LOG_NOTICE, "Exiting due to error: "
		    "Unable to allocate memory in exit_error()");
		exit(EX_OSERR);
	}
	va_end(pvar);
	syslog(LOG_NOTICE, "exit_error(): Exiting. Error '%s'", output);
	sendline(SEND_FLUSH, "-ERR %s (Exit code: %d)", output, exitcode);
	free(output);
	close(fd_in);
	close(fd_out);
	exit(exitcode);
}

void
sendline(enum send flag, const char *format, ...)
{
	int		 len;
	va_list		 pvar;
	static char	 buffer[MAXBUFLEN];
	static char	*p = buffer;

	va_start(pvar, format);
	len = vsnprintf(p, MAXBUFLEN - (p - buffer), format, pvar);
	if (len + (p - buffer) > MAXBUFLEN) {
		xwrite(buffer, p - buffer);
		p = buffer;
		len = vsnprintf(p, MAXBUFLEN - (buffer - p), format, pvar);
	}
	va_end(pvar);
	p += len;
	if (p - buffer + 3 > MAXBUFLEN) {
		xwrite(buffer, p - buffer);
		p = buffer;
	}
	*p++ = '\r';
	*p++ = '\n';
	if (flag == SEND_FLUSH) {
		xwrite(buffer, p - buffer);
		p = buffer;
	}
}

void
message(enum msg msg)
{
	switch (msg) {
	    case NOSUCH:
		sendline(SEND_FLUSH, "-ERR no such message");
		break;
	    case BADNUM:
		sendline(SEND_FLUSH, "-ERR Bad number");
		break;
	    case BADARG:
		sendline(SEND_FLUSH, "-ERR bad arguments");
		break;
	    case ALREADYDELETED:
		sendline(SEND_FLUSH, "-ERR message already deleted");
		break;
	    case MSGINVAL:
		sendline(SEND_FLUSH, "-ERR invalid message specification");
		break;
	    case CMDISABLE:
		sendline(SEND_FLUSH, "-ERR disabled by administrator");
		break;
	    case CMDINVAL:
		sendline(SEND_FLUSH, "-ERR invalid command");
		break;
	    case OUTOFRANGE:
		sendline(SEND_FLUSH, "-ERR argument out of range");
		break;
	    case CHALLENGEINVAL:
		sendline(SEND_FLUSH, "-ERR incorrect challenge");
		break;
	    case TOOFEWARGUMENTS:
		sendline(SEND_FLUSH, "-ERR incorrect number of arguments");
		break;
	    case NEEDUSERNAME:
		sendline(SEND_FLUSH, "-ERR you need to supply a username");
		break;
	    case BADPASSWORD:
		sendline(SEND_FLUSH, "-ERR incorrect password");
		break;
	    case NEEDPASSWORD:
		sendline(SEND_FLUSH, "-ERR you need to supply a password");
		break;
	}
}

void
log_stats(char *auth_string, int ret, int leave, int bytes_left, int errors,
    int del, int exp, int act_exp, int rem, int act_rem)
{
	syslog(LOG_INFO, "%s: retr %d leave %d %d byte%s %d error%s D%d "
	    "E%d(%d) R%d(%d)",
	    auth_string, ret, leave,
	    bytes_left, bytes_left == 1 ? "" : "s",
	    errors, errors == 1 ? "" : "s",
	    del,
	    exp, act_exp,
	    rem, act_rem);
}


int
getline(char **buf, int len)
{
	static char	*buffer = NULL;
	static int	buflen = -1;
	int		result;
	struct pollfd	pollfd;

	if (buflen < 0 || buflen < len) {
		buffer = xrealloc(buffer, len + 1);
		if (buflen < 0)
			memset(buffer, '\0', len + 1);
		buflen = len + 1;
	}

	pollfd.fd = fd_in;
	pollfd.events = POLLRDNORM;
	while ((result = poll(&pollfd, 1, (int)timeout * 1000))) {
		if (result < 0) {
			if (errno == EINTR) {
				return(GOT_SIGNAL);
			}
			exit_error(EX_OSERR, "Error on poll() loop: %s",
			    strerror(errno));
		}
		if (pollfd.revents & POLLHUP)
			exit_error(EX_PROTOCOL, "connection vanished");
		if ((pollfd.revents & ~POLLRDNORM) == 0) {
#ifdef USE_SSL
			if (use_ssl) {
				if ((len = SSL_read(ssl, buffer, len)) < 0)
					exit_error(EX_PROTOCOL, "Unable to read"
					    " socket '%s' - connection probably"
					    " vanished", strerror(errno));
				else {
					buffer[len] = '\0';
					break;
				}
			}
			else
#endif
				if (!fgets(buffer, len, socket_in))
					exit_error(EX_PROTOCOL, "Unable to read"
					    " socket '%s' - connection probably"
					    " vanished", strerror(errno));
				else
					break;
		}
		else
			exit_error(EX_PROTOCOL, "Unable to read socket "
			    "'%s' - connection probably vanished",
			    strerror(errno));
	}
	if (result == 0)
		return(-1);
	*buf = buffer;
	return(TRUE);
}

enum cmd
recvcmd(char **arg1, char **arg2)
{
	static char	cm[BUFLEN + 3], a1[BUFLEN + 3], a2[BUFLEN + 3];
	char		*buffer = NULL;
	int		cmd;

	if(getline(&buffer, BUFLEN + 2) < 0)
		return(TIMEDOUT);

	buffer[BUFLEN +2] = '\0';

	*arg1 = NULL;
	*arg2 = NULL;

	if (flags & MAILBOX_F_FASCIST_LOG)
		syslog(LOG_NOTICE, "FASCIST: '%s'", buffer);
	switch (sscanf(buffer, "%s %s %s\r\n", cm, a1, a2)) {
		case 3:	a2[ARGLEN] = '\0';
			*arg2 = a2;
		case 2:	a1[ARGLEN] = '\0';
			*arg1 = a1;
		case 1:	cm[CMDLEN] = '\0';
			makelowercase(cm);
			cmd = INVALCMD;
			if (!strcmp(cm, "apop"))
				cmd = APOP;
			else if (!strcmp(cm, "auth"))
				cmd = AUTH;
			else if (!strcmp(cm, "pass"))
				cmd = PASS;
			else if (!strcmp(cm, "user"))
				cmd = USER;
			else if (!strcmp(cm, "dele"))
				cmd = DELE;
			else if (!strcmp(cm, "last"))
				cmd = LAST;
			else if (!strcmp(cm, "list"))
				cmd = LIST;
			else if (!strcmp(cm, "noop"))
				cmd = NOOP;
			else if (!strcmp(cm, "quit"))
				cmd = QUIT;
			else if (!strcmp(cm, "retr"))
				cmd = RETR;
			else if (!strcmp(cm, "rset"))
				cmd = RSET;
			else if (!strcmp(cm, "stat"))
				cmd = STAT;
			else if (!strcmp(cm, "top"))
				cmd = TOP;
			else if (!strcmp(cm, "uidl"))
				cmd = UIDL;
			break;
		default:
			cmd = INVALCMD;
			*arg1 = NULL;
			*arg2 = NULL;
	}
	return(cmd);
}

char *
ntocmd(enum cmd n)
{
	switch(n) {
		case APOP:
			return("apop");
		case AUTH:
			return("auth");
		case PASS:
			return("pass");
		case USER:
			return("user");
		case DELE:
			return("dele");
		case LAST:
			return("last");
		case LIST:
			return("list");
		case NOOP:
			return("noop");
		case QUIT:
			return("quit");
		case RETR:
			return("retr");
		case RSET:
			return("rset");
		case STAT:
			return("stat");
		case TOP:
			return("top");
		case UIDL:
			return("uidl");
		case TIMEDOUT:
			return("timed out");
		case SESSION_START:
			return("Mailbox session start");
		case SESSION_END:
			return("Mailbox session end");
		case BUL_SIZE:
		case BUL_MSGS:
			return("Bulletin function");
		case INVALCMD:
		default:
			return("invalid command");
			
	}
}

#define _TMPBUFSIZE 4
char *
binhex(const void *pointer, size_t length)
{
	static char	*hex[_TMPBUFSIZE] = {NULL, NULL, NULL, NULL};
	static u_int	pos = 0;
	char		*p, *q;

	pos %= _TMPBUFSIZE;
	hex[pos] = xrealloc(hex[pos], length * 2 + 1);
	for (p = hex[pos], q = (char *)pointer; length--; q++, p += 2)
		sprintf(p, "%02x", *(unsigned char *)q);
	return(hex[pos++]);
}

char *
make_timestamp(void)
{
	struct utsname	name;
	pid_t		pid = getpid();
	time_t		tm = time((time_t *) 0);
	char		*ret;

	uname(&name);
	asprintf(&ret, "<%s@%s%s>", binhex(&pid, sizeof(pid_t)),
	    binhex(&tm, sizeof(time_t)), name.nodename);
	if (ret == NULL) {
		syslog(LOG_NOTICE, "Out of memory");
		exit_error(EX_OSERR, "Out of memory");
	}

	return(ret);
}

void
freeconnection(struct connection *cxn)
{
	if (cxn->username) {
		free(cxn->username);
		cxn->username = NULL;
	}
	if (cxn->auth_string) {
		free(cxn->auth_string);
		cxn->auth_string = NULL;
	}
	if (cxn->password) {
		free(cxn->password);
		cxn->password = NULL;
	}
	if (cxn->mailpath) {
		free(cxn->mailpath);
		cxn->mailpath = NULL;
	}
	if (cxn->bulletinpath) {
		free(cxn->bulletinpath);
		cxn->bulletinpath = NULL;
	}
}

int
cxndetails(struct connection *cxn, char *username, char *defaultrealm,
    char *maildir, char *bulletindir, int virtual, int hashdepth)
{
	char			*p;
	int			 i, n;
	size_t			 size;

	cxn->auth_string = xmalloc(strlen(username) +1);
	cxn->username = xmalloc(strlen(username) +1);
	makelowercase(username);

	strcpy(cxn->auth_string, username);
	strcpy(cxn->username, username);
	cxn->password = NULL;
	cxn->realm = NULL;

	if ((p = strchr(cxn->username, '@'))) {
		*p++ = '\0';
		if (virtual)
			cxn->realm = p;
	}
	else if (virtual && defaultrealm) {
		cxn->auth_string = xrealloc(cxn->auth_string,
		    strlen(username) + strlen(defaultrealm) + 2);
		strcat(cxn->auth_string, "@");
		strcat(cxn->auth_string, defaultrealm);
		cxn->realm = defaultrealm;
	}
	else if (virtual) {
		sendline(SEND_FLUSH, "-ERR invalid username");
		freeconnection(cxn);
		return(FALSE);
	}

	size = strlen(maildir) + strlen(cxn->username) +
		hashdepth * (hashdepth + 1) / 2 + hashdepth + 3;

	if (virtual)
		size += strlen(cxn->realm) + 1;
	if (bulletindir) {
		cxn->bulletinpath = xmalloc(strlen(bulletindir) + 1);
		strcpy(cxn->bulletinpath, bulletindir);
	}
	cxn->mailpath = xcalloc(1, size);
	strcpy(cxn->mailpath, maildir);
	if (virtual) {
		strcat(cxn->mailpath, "/");
		strcat(cxn->mailpath, cxn->realm);
	}
	strcat(cxn->mailpath, "/");
	for (n = 1, i = hashdepth; i--; n++) {
		if (!cxn->username[n-1])
			n--;
		strncat(cxn->mailpath, cxn->username, n);
		strcat(cxn->mailpath, "/");
	}
	strcat(cxn->mailpath, cxn->username);
	return(TRUE);
}

time_t
atosec(const char *tstring)
{
	char	*p, *q;
	char	chars[] = "sSmmhHdDwWMMyY";
	time_t	tm, i, 
		factor[] = {1, 60, 3600, 86400, 604800, 2629800, 31557600};

	q = (char *)tstring;
	tm = 0;
	for (;;) {
		i = strtol(p = q, (char **)&q, 10);
		if (!(q && p != q && (p = strchr(chars, *q))))
			break;
		tm += i * factor[(p - chars) / 2];
		q++;
	}
	if (!p)
		return(-1);
	return (tm);
}

void
poputil_init(FILE *in, FILE *out, time_t tmout, int flag)
{
	fd_in = fileno(in);
	fd_out = fileno(out);
	socket_in = in;
	timeout = tmout;
	flags = flag;
}

#ifdef USE_SSL
void
ssl_init(int do_ssl, char *etc, char *cert, char *key)
{
	int	facility = LOG_NOTICE;

	use_ssl = do_ssl;
	if (use_ssl) {
		ssl_cert = xmalloc(strlen(etc) + strlen(cert) + 2);
		sprintf(ssl_cert, "%s/%s", etc, cert);
		ssl_key = xmalloc(strlen(etc) + strlen(key) + 2);
		sprintf(ssl_key, "%s/%s", etc, key);
		SSL_load_error_strings();
		SSLeay_add_ssl_algorithms();
		meth = SSLv23_server_method();
		ctx = SSL_CTX_new(meth);
		if (!ctx) {
			ERR_print_errors_fp(stderr);
			exit(2);
		}
		if (SSL_CTX_use_certificate_file(ctx, ssl_cert,
		    SSL_FILETYPE_PEM) <= 0) {
			ERR_print_errors_fp(stderr);
			exit(3);
		}
		if (SSL_CTX_use_PrivateKey_file(ctx, ssl_key,
		    SSL_FILETYPE_PEM) <= 0) {
			ERR_print_errors_fp(stderr);
			exit(4);
		}
		if (!SSL_CTX_check_private_key(ctx)) {
			syslog(facility, "Private key does not match "
			    "certificate public key");
			exit(5);
	        }
		SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
	}
}
#endif

void
poputil_end(void)
{
	close(fd_in);
	close(fd_out);
#ifdef USE_SSL
	if (use_ssl) {
		SSL_free(ssl);
		SSL_CTX_free(ctx);
	}
#endif
}


syntax highlighted by Code2HTML, v. 0.9.1