/*
* 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