/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef USE_SSL #include #include #include #include #include #include #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 }