diff options
-rw-r--r-- | header.c | 346 | ||||
-rw-r--r-- | header.h | 22 | ||||
-rw-r--r-- | lmtp.c | 717 | ||||
-rw-r--r-- | lmtp.h | 185 | ||||
-rw-r--r-- | lmtpd.c | 314 | ||||
-rw-r--r-- | sievecmd.c | 371 | ||||
-rw-r--r-- | sievecmd.h | 39 | ||||
-rw-r--r-- | sort.h | 33 | ||||
-rw-r--r-- | sort/Makefile.am | 25 | ||||
-rw-r--r-- | sort/sort.c | 337 | ||||
-rw-r--r-- | sort/sortsieve.c | 322 | ||||
-rw-r--r-- | sort/sortsieve.h | 23 | ||||
-rwxr-xr-x | sql/migrate_singe_user.py | 236 | ||||
-rw-r--r-- | timsieve.c | 736 | ||||
-rw-r--r-- | timsieve.h | 79 | ||||
-rw-r--r-- | timsieved.c | 315 |
16 files changed, 4100 insertions, 0 deletions
diff --git a/header.c b/header.c new file mode 100644 index 00000000..d781d4a8 --- /dev/null +++ b/header.c @@ -0,0 +1,346 @@ +/* $Id$ + * (c) 2000-2002 IC&S, The Netherlands + * + * Header.c implements functions to read an email header + * and parse out certain goodies, such as deliver-to + * fields and common fields for the fast header cache + * */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "dbmail.h" +#include "list.h" +#include "auth.h" +#include "mime.h" +#include "header.h" +#include "db.h" +#include "debug.h" +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +extern struct list mimelist; +extern struct list users; +extern struct list smtpItems; + +#define HEADER_BLOCK_SIZE 1024 + +/* Must be at least 998 or 1000 by RFC's */ +#define MAX_LINE_SIZE 1024 + +/* Reads from the specified pipe until either a lone carriage + * return or lone period stand on a line by themselves. The + * number of newlines is recorded along the way. The variable + * "header" should be passed by & reference, and should be + * defined (duh) but not malloc'ed (honest) before calling. + * + * The caller is responsible for free'ing header, even upon error. + * + * Return values: + * 1 on success + * 0 on failure + * */ +int read_header(FILE *instream, u64_t *newlines, u64_t *headersize, char **header) +{ + char *tmpline; + char *tmpheader; + int usedmem=0, linemem=0; + int myeof=0; + int allocated_blocks=1; + + *headersize = 0; + *newlines = 0; + + memtst ((tmpheader = (char *)my_malloc(HEADER_BLOCK_SIZE))==NULL); + memtst ((tmpline = (char *)my_malloc(MAX_LINE_SIZE))==NULL); + + /* Resetting */ + memset (tmpline, '\0', MAX_LINE_SIZE); + memset (tmpheader, '\0', HEADER_BLOCK_SIZE); + + /* here we will start a loop to read in the message header */ + /* the header will be everything up until \n\n or an EOF of */ + /* in_stream (instream) */ + + trace (TRACE_INFO, "read_header(): readheader start\n"); + + while (!feof(instream) && !myeof) + { + /* fgets will read until \n occurs, and \n is *included* in tmpline */ + tmpline = fgets (tmpline, MAX_LINE_SIZE, instream); + linemem = strlen(tmpline); + (*headersize) += linemem; + (*newlines)++; + + if (ferror(instream)) + { + trace(TRACE_ERROR,"read_header(): error on instream: [%s]", strerror(errno)); + if (tmpline != NULL) + my_free(tmpline); + /* FIXME: Make sure that the caller knows to free + * the header block even if there's been an error! */ + return -1; + } + + /* The end of the header could be \n\n, \r\n\r\n, + * or \r\n.\r\n, in the accidental case that we + * ate the whole SMTP message, too! */ + if (strcmp(tmpline, ".\r\n") == 0) + { + /* This is the end of the message! */ + trace (TRACE_DEBUG,"read_header(): single period found"); + myeof = 1; + } + else if (strcmp(tmpline, "\n") == 0 || strcmp(tmpline, "\r\n") == 0) + { + /* We've found the end of the header */ + trace (TRACE_DEBUG,"read_header(): single blank line found"); + myeof = 1; + } + + /* Even if we hit the end of the header, don't forget to copy the extra + * returns. They will always be needed to separate the header from the + * message during any future retrieval of the fully concatenated message. + * */ + + trace (TRACE_DEBUG,"read_header(): copying line into header"); + + /* If this happends it's a very big header */ + if (usedmem + linemem > (allocated_blocks*HEADER_BLOCK_SIZE)) + { + /* Update block counter */ + allocated_blocks++; + trace (TRACE_DEBUG,"read_header(): mem current: [%d] reallocated to [%d]", + usedmem, allocated_blocks*HEADER_BLOCK_SIZE); + memtst((tmpheader = (char *)realloc(tmpheader, allocated_blocks*HEADER_BLOCK_SIZE))==NULL); + } + + /* This *should* always happen, but better safe than overflowing! */ + if (usedmem + linemem < (allocated_blocks*HEADER_BLOCK_SIZE)) + { + /* Copy starting at the current usage offset */ + strncpy( (tmpheader+usedmem), tmpline, linemem); + usedmem += linemem; + + /* Resetting strlen for tmpline */ + tmpline[0] = '\0'; + linemem=0; + } + } + + trace (TRACE_DEBUG, "read_header(): readheader done"); + trace (TRACE_DEBUG, "read_header(): found header [%s] of len [%d] using mem [%d]", + tmpheader, strlen(tmpheader), usedmem); + + if (tmpline != NULL) + my_free(tmpline); + + if (usedmem==0) + { + trace (TRACE_STOP, "read_header(): no valid mail header found\n"); + return 0; + } + + /* Assign to the external variable */ + *header = tmpheader; + + /* The caller is responsible for freeing header/tmpheader. */ + + trace (TRACE_INFO, "read_header(): function successfull\n"); + return 1; +} + + + +/* + * read_header_process() + * + * Reads in a mail header (from instream, reading is done until '\n\n' is encountered). + * If field != NULL scanning is done for delivery on that particular field. + * + * Data is saved in hdrdata which should be capable of holding at least READ_BLOCK_SIZE characters. + * + * returns data cnt on success, -1 on failure + */ +int read_header_process(FILE *instream, struct list *userids, struct list *bounces, struct list *fwds, + const char *field, char *hdrdata, u64_t *newlines, char **bounce_path) +{ + int len = field ? strlen(field) : 0; + char *left, *right, *curr, save, *line; + char *frompath = 0; + unsigned cnt = 0; + *newlines = 0; + *bounce_path = 0; + + while (!feof(instream) && !ferror(instream) && cnt < READ_BLOCK_SIZE) + { + line = &hdrdata[cnt]; /* write directly to hdrdata */ + fgets(line, READ_BLOCK_SIZE - cnt, instream); + (*newlines)++; + + cnt += strlen(line); + + if (strcmp(line, "\n") == 0) + break; + + if (field && strncasecmp(line, field, len) == 0 && line[len] == ':' && line[len+1] == ' ') + { + /* found the field we're scanning for */ + trace(TRACE_DEBUG, "read_header_process(): found field"); + + curr = &line[len]; + + while (curr && *curr) + { + left = strchr(curr, '@'); + if (!left) + break; + + right = left; + + /* walk to the left */ + while (left != line && left[0]!='<' && left[0]!=' ' && left[0]!='\0' && left[0]!=',') + left--; + + left++; /* walked one back too far */ + + /* walk to the right */ + while (right[0]!='>' && right[0]!=' ' && right[0]!='\0' && right[0]!=',') + right++; + + save = *right; + *right = 0; /* terminate string */ + + if (add_address(left, userids, bounces, fwds) != 0) + trace(TRACE_ERROR,"read_header_process(): could not add [%s]", left); + + trace(TRACE_DEBUG,"read_header_process(): processed [%s]", left); + *right = save; + curr = right; + } + } + else if (field && !(*bounce_path) && strncasecmp(line, "return-path", strlen("return-path")) == 0) + { + /* found return-path */ + *bounce_path = (char*)my_malloc(strlen(line)); + if (!(*bounce_path)) + return -1; + + left = strchr(line, ':'); + if (left) + strcpy(*bounce_path, &left[1]); + else + { + my_free(bounce_path); + bounce_path = 0; + } + } + else if (field && !(*bounce_path) && !frompath && strncasecmp(line, "from", strlen("from")) == 0) + { + /* found from field */ + frompath = (char*)my_malloc(strlen(line)); + if (!frompath) + return -1; + + left = strchr(line, ':'); + if (left) + strcpy(frompath, &left[1]); + else + { + my_free(frompath); + frompath = 0; + } + } + } + + if (frompath) + { + if (!(*bounce_path)) + *bounce_path = frompath; + else + my_free(frompath); + } + + trace(TRACE_DEBUG,"read_header_process(): found bounce path [%s]", *bounce_path ? *bounce_path : "<<none>>"); + + return cnt; +} + + +/* + * add_address() + * + * takes an e-mail address and finds the correct delivery for it: + * internal (numeric id), bounce, forward + * + * returns 0 on success, -1 on failure + */ +int add_address(const char *address, struct list *userids, struct list *bounces, struct list *fwds) +{ + char *domain; + + if (auth_check_user_ext(address, userids, fwds, -1) == 0) + { + /* not in alias table + * check for a domain fwd first; if none present + * then make it a bounce + */ + + domain = strchr(address, '@'); + if (!domain) + { + /* ?? no '@' in address ? */ + trace(TRACE_ERROR, "add_address(): got invalid address [%s]", address); + } + else + { + if (auth_check_user_ext(domain, userids, fwds, -1) == 0) + { + /* ok no domain fwds either --> bounce */ + if (list_nodeadd(bounces, address, strlen(address)+1) == 0) + { + trace(TRACE_ERROR, "add_address(): could not add bounce [%s]", address); + return -1; + } + } + } + } + + return 0; +} + + +/* + * add_username() + * + * adds the (numeric) ID of the user uname to the list of ids. + * + * returns 0 on success, -1 on failure + */ +int add_username(const char *uname, struct list *userids) +{ + u64_t uid; + + switch(auth_user_exists(uname, &uid)) + { + case -1: + trace(TRACE_ERROR,"add_username(): error verifying user existence"); + return -1; + case 0: + trace(TRACE_INFO,"add_username(): non-existent user specified"); + return -1; + default: + trace(TRACE_DEBUG,"add_username(): adding user [%s] id [%llu] to list", uname, uid); + if (list_nodeadd(userids, &uid, sizeof(uid)) == NULL) + { + trace(TRACE_ERROR,"add_username(): out of memory"); + list_freelist(&userids->start); + return -1; + } + } + + return 0; +} + diff --git a/header.h b/header.h new file mode 100644 index 00000000..096f4452 --- /dev/null +++ b/header.h @@ -0,0 +1,22 @@ +/* $Id$ + * (c) 2000-2002 IC&S, The Netherlands */ + +#ifndef _HEADER_H +#define _HEADER_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "list.h" + +int read_header(FILE *instream, u64_t *newlines, u64_t *headersize, char **header); +int read_header_process(FILE *instream, struct list *userids, + struct list *bounces, struct list *fwds, + const char *field, char *hdrdata, + u64_t *newlines, char **bounce_path); +int add_address(const char *address, struct list *userids, + struct list *bounces, struct list *fwds); +int add_username(const char *uname, struct list *userids); + +#endif @@ -0,0 +1,717 @@ +/* $Id$ + * (c) 2000-2002 IC&S, The Netherlands + * + * implementation for lmtp commands according to RFC 1081 */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "dbmail.h" +#include "lmtp.h" +#include "pipe.h" +#include "header.h" +#include "db.h" +#include "debug.h" +#include "dbmailtypes.h" +#include "auth.h" +#include "clientinfo.h" +#include "lmtp.h" +#ifdef PROC_TITLES +#include "proctitleutils.h" +#endif + +#define INCOMING_BUFFER_SIZE 512 + +/* default timeout for server daemon */ +#define DEFAULT_SERVER_TIMEOUT 300 + +/* max_errors defines the maximum number of allowed failures */ +#define MAX_ERRORS 3 + +/* max_in_buffer defines the maximum number of bytes that are allowed to be + * in the incoming buffer */ +#define MAX_IN_BUFFER 255 + +/* This one needs global score for bounce.c */ +struct list mimelist; + +/* These are needed across multiple calls to lmtp() */ +struct list rcpt, userids, fwds; +char *envelopefrom = NULL; + +/* allowed lmtp commands */ +const char *commands [] = +{ + "LHLO", "QUIT", "RSET", "DATA", "MAIL", + "VRFY", "EXPN", "HELP", "NOOP", "RCPT" +}; + +const char validchars[] = +"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" +"_.!@#$%^&*()-+=~[]{}<>:;\\/ "; + +char myhostname[64]; + +int lmtp_handle_connection(clientinfo_t *ci) +{ + /* + Handles connection and calls + lmtp command handler + */ + + int done = 1; /* loop state */ + char *buffer = NULL; /* connection buffer */ + int cnt; /* counter */ + + PopSession_t session; /* current connection session */ + + /* setting Session variables */ + session.error_count = 0; + + session.username = NULL; + session.password = NULL; + + session.SessionResult = 0; + + /* reset counters */ + session.totalsize = 0; + session.virtual_totalsize = 0; + session.totalmessages = 0; + session.virtual_totalmessages = 0; + + + /* getting hostname */ + gethostname(myhostname,64); + myhostname[63] = 0; /* make sure string is terminated */ + + buffer=(char *)my_malloc(INCOMING_BUFFER_SIZE*sizeof(char)); + + if (!buffer) + { + trace(TRACE_MESSAGE,"lmtp_handle_connection(): Could not allocate buffer"); + return 0; + } + + if (ci->tx) + { + /* sending greeting */ + fprintf(ci->tx,"220 %s DBMail LMTP service ready to rock\r\n", + myhostname); + fflush(ci->tx); + } + else + { + trace(TRACE_MESSAGE,"lmtp_handle_connection(): TX stream is null!"); + return 0; + } + + while (done > 0) + { + /* set the timeout counter */ + alarm(ci->timeout); + + /* clear the buffer */ + memset(buffer, 0, INCOMING_BUFFER_SIZE); + + for (cnt=0; cnt < INCOMING_BUFFER_SIZE-1; cnt++) + { + do + { + clearerr(ci->rx); + fread(&buffer[cnt], 1, 1, ci->rx); + + /* leave, an alarm has occured during fread */ + if (!ci->rx) return 0; + } while (ferror(ci->rx) && errno == EINTR); + + if (buffer[cnt] == '\n' || feof(ci->rx) || ferror(ci->rx)) + { + buffer[cnt+1] = '\0'; + break; + } + } + + if (feof(ci->rx) || ferror(ci->rx)) + { + /* check client eof */ + done = -1; + } + else + { + /* reset function handle timeout */ + alarm(0); + /* handle lmtp commands */ + done = lmtp(ci->tx, ci->rx, buffer, ci->ip, &session); + } + fflush(ci->tx); + } + + /* memory cleanup */ + my_free(buffer); + buffer = NULL; + + /* reset timers */ + alarm(0); + __debug_dumpallocs(); + + return 0; +} + + +int lmtp_reset(PopSession_t *session) + { + /* Free the lists and reinitialize + * but only if they were previously + * initialized by LMTP_LHLO... */ + if( session->state == LHLO ) + { + list_freelist( &rcpt.start ); + list_init( &rcpt ); + list_freelist( &userids.start ); + list_init( &userids ); + list_freelist( &fwds.start ); + list_init( &fwds ); + } + + if( envelopefrom != NULL ) + { + my_free( envelopefrom ); + } + envelopefrom = NULL; + + session->state = LHLO; + + return 1; + } + + +int lmtp_error(PopSession_t *session, void *stream, const char *formatstring, ...) +{ + va_list argp; + + if (session->error_count>=MAX_ERRORS) + { + trace(TRACE_MESSAGE,"lmtp_error(): too many errors (MAX_ERRORS is %d)",MAX_ERRORS); + fprintf((FILE *)stream, "500 Too many errors, closing connection.\r\n"); + session->SessionResult = 2; /* possible flood */ + lmtp_reset(session); + return -3; + } + else + { + va_start(argp, formatstring); + vfprintf((FILE *)stream, formatstring, argp); + va_end(argp); + } + + trace(TRACE_DEBUG,"lmtp_error(): an invalid command was issued"); + session->error_count++; + return 1; +} + + +int lmtp(void *stream, void *instream, char *buffer, char *client_ip, PopSession_t *session) +{ + /* returns values: + * 0 to quit + * -1 on failure + * 1 on success */ + char *command, *value; + int cmdtype; + int indx=0; + + /* buffer overflow attempt */ + if (strlen(buffer) > MAX_IN_BUFFER) + { + trace(TRACE_DEBUG, "lmtp(): buffer overflow attempt"); + return -3; + } + + /* check for command issued */ + while (strchr(validchars, buffer[indx])) + indx++; + + /* end buffer */ + buffer[indx]='\0'; + + trace(TRACE_DEBUG,"lmtp(): incoming buffer: [%s]",buffer); + + command = buffer; + + value = strstr(command," "); /* look for the separator */ + + if (value!=NULL) + { + *value = '\0'; /* set a \0 on the command end */ + value++; /* skip space */ + + if (strlen(value) == 0) + { + value=NULL; /* no value specified */ + } + else + { + trace(TRACE_DEBUG,"lmtp(): command issued :cmd [%s], value [%s]\n",command, value); + } + } + + for (cmdtype = LMTP_STRT; cmdtype < LMTP_END; cmdtype ++) + if (strcasecmp(command, commands[cmdtype]) == 0) break; + + trace(TRACE_DEBUG,"lmtp(): command looked up as commandtype %d", cmdtype); + + /* commands that are allowed to have no arguments */ + if ((value==NULL) && + !( + (cmdtype==LMTP_LHLO) || (cmdtype==LMTP_DATA) || + (cmdtype==LMTP_RSET) || (cmdtype==LMTP_QUIT) || + (cmdtype==LMTP_NOOP) || (cmdtype==LMTP_HELP) + )) + { + return lmtp_error(session, stream, "500 This command requires an argument.\r\n"); + } + + switch (cmdtype) + { + case LMTP_QUIT : + { + fprintf((FILE *)stream, "221 %s BYE\r\n", myhostname); + lmtp_reset(session); + return 0; /* return 0 to cause the connection to close */ + } + case LMTP_NOOP : + { + fprintf((FILE *)stream, "250 OK\r\n"); + return 1; + } + case LMTP_RSET : + { + fprintf((FILE *)stream, "250 OK\r\n"); + lmtp_reset(session); + return 1; + } + case LMTP_LHLO : + { + /* Reply wth our hostname and a list of features. + * The RFC requires a couple of SMTP extensions + * with a MUST statement, so just hardcode them. + * */ + fprintf((FILE *)stream, + "250-%s\r\n" + "250-PIPELINING\r\n" + "250-ENHANCEDSTATUSCODES\r\n" + /* This is a SHOULD implement: + * "250-8BITMIME\r\n" + * Might as well do these, too: + * "250-CHUNKING\r\n" + * "250-BINARYMIME\r\n" + * */ + "250 SIZE\r\n", myhostname ); + /* Free the recipients list and reinitialize it */ + // list_freelist( &rcpt.start ); + list_init( &rcpt ); + // list_freelist( &userids.start ); + list_init( &userids ); + // list_freelist( &fwds.start ); + list_init( &fwds ); + + session->state = LHLO; + return 1; + } + case LMTP_HELP : + { + int helpcmd; + + if (value != NULL) + for (helpcmd = LMTP_STRT; helpcmd < LMTP_END; helpcmd++) + if (strcasecmp(value, commands[helpcmd]) == 0) break; + + trace(TRACE_DEBUG,"lmtp(): LMTP_HELP requested for commandtype %d", helpcmd); + + if( (helpcmd==LMTP_LHLO) || (helpcmd==LMTP_DATA) || + (helpcmd==LMTP_RSET) || (helpcmd==LMTP_QUIT) || + (helpcmd==LMTP_NOOP) || (helpcmd==LMTP_HELP) ) + { + fprintf((FILE *)stream, LMTP_HELP_TEXT[helpcmd]); + } + else + { + fprintf((FILE *)stream, LMTP_HELP_TEXT[LMTP_STRT]); + } + + return 1; + } + case LMTP_VRFY : + { + /* RFC 2821 says this SHOULD be implemented... + * and the goal is to say if the given address + * is a valid delivery address at this server. */ + fprintf((FILE *)stream, "502 Command not implemented\r\n" ); + return 1; + } + case LMTP_EXPN: + { + /* RFC 2821 says this SHOULD be implemented... + * and the goal is to return the membership + * of the specified mailing list. */ + fprintf((FILE *)stream, "502 Command not implemented\r\n" ); + return 1; + } + case LMTP_MAIL: + { + /* We need to LHLO first because the client + * needs to know what extensions we support. + * */ + if (session->state != LHLO) + { + fprintf((FILE *)stream, "550 Command out of sequence.\r\n"); + } + else if (envelopefrom != NULL) + { + fprintf((FILE *)stream, "500 Sender already received. Use RSET to clear.\r\n"); + } + else + { + /* First look for an email address. + * Don't bother verifying or whatever, + * just find something between angle brackets! + * */ + int goodtogo=1; + size_t tmplen=0; + char *tmpleft=NULL, *tmpright=NULL, *tmpbody=NULL; + + tmpleft = value; + tmpright = value + strlen(value); + + /* eew pointer math... inspired by injector.c */ + + while (tmpleft[0] != '<' && tmpleft < tmpright ) + tmpleft++; + while (tmpright[0] != '>' && tmpright > tmpleft ) + tmpright--; + + /* Step left up to skip '<' left angle bracket */ + tmpleft++; + + tmplen = tmpright - tmpleft; + + /* Second look for a BODY keyword. + * See if it has an argument, and if we + * support that feature. Don't give an OK + * if we can't handle it yet, like 8BIT! + * */ + + /* Find the '=' following the address + * then advance one character past it + * (but only if there's more string!) + * */ + tmpbody = strstr(tmpright, "="); + if (tmpbody != NULL) + if (strlen(tmpbody)) + tmpbody++; + + /* This is all a bit nested now... */ + if (tmplen < 1) + { + fprintf((FILE *)stream,"500 No address found.\r\n" ); + } + else if (tmpbody != NULL) + { + /* See RFC 3030 for the best + * description of this stuff. + * */ + if (strlen(tmpbody) < 4) + { + /* Caught */ + } + else if (0 == strcasecmp(tmpbody, "7BIT")) + { + /* Sure fine go ahead. */ + goodtogo = 1; // Not that it wasn't 1 already ;-) + } + /* 8BITMIME corresponds to RFC 1652, + * BINARYMIME corresponds to RFC 3030. + * */ + else if (strlen(tmpbody) < 8) + { + /* Caught */ + } + else if (0 == strcasecmp(tmpbody, "8BITMIME")) + { + /* We can't do this yet. */ + /* session->state = BIT8; + * */ + fprintf((FILE *)stream,"500 Please use 7BIT MIME only.\r\n"); + goodtogo = 0; + } + else if (strlen(tmpbody) < 10) + { + /* Caught */ + } + else if (0 == strcasecmp(tmpbody, "BINARYMIME")) + { + /* We can't do this yet. */ + /* session->state = BDAT; + * */ + fprintf((FILE *)stream,"500 Please use 7BIT MIME only.\r\n" ); + goodtogo = 0; + } + } + + if (goodtogo) + { + /* Sure fine go ahead. */ + memtst((envelopefrom=(char *)my_malloc(tmplen+1))==NULL); + memset(envelopefrom,0,tmplen+1); + strncpy(envelopefrom,tmpleft,tmplen); + // envelopefrom[tmplen+1] = '\0'; + fprintf((FILE *)stream,"250 Sender <%s> OK\r\n", envelopefrom ); + } + } + return 1; + } + case LMTP_RCPT : + { + /* This would be the non-piplined version... + else if (0 < auth_check_user_ext(value, userids, fwds, -1)) + { + fprintf((FILE *)stream, "250 OK\r\n" ); + } + else + { + fprintf((FILE *)stream, "550 No such user here\r\n" ); + } + return 1; + */ + + if (session->state != LHLO) + { + fprintf((FILE *)stream, "550 Command out of sequence.\r\n"); + } + else + { + size_t tmplen; + char *tmpleft, *tmpright, *tmprcpt; + + tmpleft = value; + tmpright = value + strlen(value); + + /* eew pointer math... inspired by injector.c */ + + while (tmpleft[0] != '<' && tmpleft < tmpright ) + tmpleft++; + while (tmpright[0] != '>' && tmpright > tmpleft ) + tmpright--; + + /* Step left up to skip '<' left angle bracket */ + tmpleft++; + + tmplen = tmpright - tmpleft; + + if (tmplen < 1) + { + fprintf((FILE *)stream,"500 No address found.\r\n" ); + } + else + { + /* Note that list_nodeadd cannot NULL terminate + * because it does not know what kind of data it gets! */ + memtst((tmprcpt=(char *)my_malloc(tmplen+1))==NULL); + memset(tmprcpt,0,tmplen+1); + strncpy(tmprcpt,tmpleft,tmplen); + // tmprcpt[tmplen+1] = 0; + + /* Just add it to the list, and process at DATA time */ + /* Make sure to pass the terminator at +1 */ + list_nodeadd(&rcpt, tmprcpt, tmplen+1); + + /* Is there a way to know if the client wants + * to pipeline or not? The RFC for LMTP implies that + * they will pipeline, because pipelining is mandatory + * for LMTP servers... but... I dunno? What if they're waiting? + */ + // fprintf((FILE *)stream,"250 Recipient <%s> OK\r\n", tmprcpt ); + + my_free(tmprcpt); + } + } + return 1; + } + /* Here's where it gets really exciting! */ + case LMTP_DATA: + { + // if (session->state != DATA || session->state != BIT8) + if (session->state != LHLO) + { + fprintf((FILE *)stream, "550 Command out of sequence\r\n" ); + } + else if (list_totalnodes(&rcpt) < 1) + { + fprintf((FILE *)stream, "554 No valid recipients\r\n" ); + } + else + { + struct element *tmpnode; + + /* The replies MUST be in the order received */ + rcpt.start = list_reverse(rcpt.start); + + tmpnode = list_getstart(&rcpt); + while( tmpnode != NULL ) + { + if (auth_check_user_ext(tmpnode->data, &userids, &fwds, -1) > 0 ) + { + fprintf((FILE *)stream, "250 Recipient <%s> OK\r\n", (char *)tmpnode->data ); + } + else + { + fprintf((FILE *)stream, "550 Recipient <%s> FAIL\r\n", (char *)tmpnode->data ); + } + tmpnode = tmpnode->nextnode; + } + + /* Now we have a list of recipients! */ + /* Let the client know if they should continue... */ + + if (list_totalnodes(&userids) > 0 || list_totalnodes(&fwds) > 0) + { + fprintf((FILE *)stream, "354 Start mail input; end with <CRLF>.<CRLF>\r\n" ); + } + else + { + fprintf((FILE *)stream, "554 No valid recipients.\r\n" ); + return 1; + } + + /* If we returned due to no recipients, and the remote + * system starts sending a message... well... they'll + * get disconnected pretty quickly from max_errors. + * */ + { + char *header = NULL; + u64_t headersize=0, newlines=0; + u64_t dummyidx=0,dummysize=0; + struct list fromlist, headerfields, errusers; + struct element *tmpnode_rcpt; + struct element *tmpnode_errs; + + list_init(&errusers); + list_init(&mimelist); + list_init(&fromlist); + list_init(&headerfields); + + if (envelopefrom != NULL) + list_nodeadd(&fromlist, envelopefrom, strlen(envelopefrom)); + else + { + trace(TRACE_DEBUG,"main(): envelopefrom is empty so no go"); + fprintf((FILE *)stream, "554 No valid sender.\r\n" ); + return 1; + } + + if (!read_header((FILE *)instream, &newlines, &headersize, &header)) + { + trace(TRACE_ERROR,"main(): fatal error from read_header()"); + fprintf((FILE *)stream, "500 Error reading header.\r\n" ); + return 1; + } + + trace(TRACE_ERROR,"main(): lines of read_header() header is [%d]", newlines); + + if (header != NULL) + { + trace(TRACE_ERROR,"main(): size of read_header() header is [%d]", headersize); + } + else + { + trace(TRACE_ERROR,"main(): read_header() returned a null header [%s]", header); + fprintf((FILE *)stream, "500 Error reading header.\r\n" ); + return 1; + } + + /* Parse the list and scan for field and content */ + if (mime_readheader(header, &dummyidx, &mimelist, &dummysize) < 0) + { + trace(TRACE_ERROR,"main(): fatal error from mime_readheader()"); + return 1; + } + + /* FIXME: A negative return code from insert_messages() means + * that delivery died halfway through. There may be much more + * data coming from the client that we need to discard after + * giving back an unsuccessful return code. */ + insert_messages((FILE *)instream, header, headersize, + &rcpt, &errusers, + &fromlist, 0, + NULL, &headerfields); + if (header != NULL) + my_free(header); + + /* Use the 250 code if 1 or more deliveries were successful, + * and report the errors individually later + * Use the 503 code is 0 deliveries went through. + * + * This is a weird little assumption... that if there + * was some problem assembling the list of errors, + * assume everyone failed. Basically it's all we + * can do since otherwise we can't know who did or + * did not fail anyways! + * */ + if (rcpt.total_nodes != errusers.total_nodes) + { + fprintf((FILE *)stream, "503 Message not received %ld FAIL\r\n", errusers.total_nodes ); + + tmpnode_rcpt = list_getstart(&rcpt); + while (tmpnode_rcpt != NULL ) + { + fprintf((FILE *)stream, "450 Recipient <%s> FAIL\r\n", (char *)tmpnode_rcpt->data ); + tmpnode_rcpt = tmpnode_rcpt->nextnode; + } + } + else + { + fprintf((FILE *)stream, "250 Message received OK\r\n" ); + + /* The replies MUST be in the order received */ + rcpt.start = list_reverse(rcpt.start); + + /* The errors MUST be in the same order as rcpt */ + errusers.start = list_reverse(errusers.start); + + tmpnode_rcpt = list_getstart(&rcpt); + tmpnode_errs = list_getstart(&errusers); + while (tmpnode_rcpt != NULL && tmpnode_errs != NULL) + { + /* These are evil magic numbers + * which must match pipe.c + * FIXME: define these! + * */ + switch ((int)tmpnode_errs->data) + { + case 1: + fprintf((FILE *)stream, "250 Recipient <%s> OK\r\n", (char *)tmpnode_rcpt->data ); + break; + case 0: + fprintf((FILE *)stream, "450 Recipient <%s> FAIL\r\n", (char *)tmpnode_rcpt->data ); + break; + default: + fprintf((FILE *)stream, "450 Recipient <%s> FAIL\r\n", (char *)tmpnode_rcpt->data ); + break; + } + tmpnode_rcpt = tmpnode_rcpt->nextnode; + tmpnode_errs = tmpnode_errs->nextnode; + } + } + } + } + return 1; + } + default : + { + return lmtp_error(session, stream,"500 What are you trying to say here?\r\n"); + } + } + return 1; +} + @@ -0,0 +1,185 @@ +/* $Id$ + * (c) 2000-2002 IC&S, The Netherlands + * + * Copied from pop3.h as a starting point - Aaron Stone, 4/14/03 + * This defines some default messages for LMTP */ + +#ifndef _LMTP_H +#define _LMTP_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <time.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <sys/wait.h> +#include <signal.h> +#include <time.h> + +#include "misc.h" +#include "list.h" +#include "debug.h" +#include "dbmail.h" +#include "dbmailtypes.h" +#include "clientinfo.h" + +/* processes */ + +#define MAXCHILDREN 5 +#define DEFAULT_CHILDREN 5 + +#define LMTP_DEF_MAXCONNECT 1500 + +/* connection */ + +#define STRT 1 +#define LHLO 2 +#define DATA 3 +#define BIT8 4 +#define BDAT 5 + +/* allowed lmtp commands, from lmtp.c +const char *commands [] = +{ + "LHLO", "QUIT", "RSET", "DATA", "MAIL", + "VRFY", "EXPN", "HELP", "NOOP", "RCPT" +}; */ + +#define LMTP_STRT 0 /* lower bound of array - 0 */ +#define LMTP_LHLO 0 +#define LMTP_QUIT 1 +#define LMTP_RSET 2 +#define LMTP_DATA 3 +#define LMTP_MAIL 4 +#define LMTP_VRFY 5 +#define LMTP_EXPN 6 +#define LMTP_HELP 7 +#define LMTP_NOOP 8 +#define LMTP_RCPT 9 +#define LMTP_END 10 /* upper bound of array + 1 */ + +int lmtp(void *stream, void *instream, char *buffer, char *client_ip, PopSession_t *session); +int lmtp_handle_connection(clientinfo_t *ci); + +/* + * Enhanced Status Codes + * From RFC 1893 + * + * Top level codes: + * 2.X.X Success + * 4.X.X Persistent Transient Failure + * 5.X.X Permanent Failure + * + * Second the Third level codes: + * Address Status + * X.1.0 Other address status + * X.1.1 Bad destination mailbox address + * X.1.2 Bad destination system address + * X.1.3 Bad destination mailbox address syntax + * X.1.4 Destination mailbox address ambiguous + * X.1.5 Destination mailbox address valid + * X.1.6 Mailbox has moved + * X.1.7 Bad sender's mailbox address syntax + * X.1.8 Bad sender's system address + * Mailbox Status + * X.2.0 Other or undefined mailbox status + * X.2.1 Mailbox disabled, not accepting messages + * X.2.2 Mailbox full + * X.2.3 Message length exceeds administrative limit. + * X.2.4 Mailing list expansion problem + * Mail System Status + * X.3.0 Other or undefined mail system status + * X.3.1 Mail system full + * X.3.2 System not accepting network messages + * X.3.3 System not capable of selected features + * X.3.4 Message too big for system + * Network and Routing Status + * X.4.0 Other or undefined network or routing status + * X.4.1 No answer from host + * X.4.2 Bad connection + * X.4.3 Routing server failure + * X.4.4 Unable to route + * X.4.5 Network congestion + * X.4.6 Routing loop detected + * X.4.7 Delivery time expired + * Mail Delivery Protocol Status + * X.5.0 Other or undefined protocol status + * X.5.1 Invalid command + * X.5.2 Syntax error + * X.5.3 Too many recipients + * X.5.4 Invalid command arguments + * X.5.5 Wrong protocol version + * Message Content or Message Media Status + * X.6.0 Other or undefined media error + * X.6.1 Media not supported + * X.6.2 Conversion required and prohibited + * X.6.3 Conversion required but not supported + * X.6.4 Conversion with loss performed + * X.6.5 Conversion failed + * Security or Policy Status + * X.7.0 Other or undefined security status + * X.7.1 Delivery not authorized, message refused + * X.7.2 Mailing list expansion prohibited + * X.7.3 Security conversion required but not possible + * X.7.4 Security features not supported + * X.7.5 Cryptographic failure + * X.7.6 Cryptographic algorithm not supported + * X.7.7 Message integrity failure + */ + +/* Help */ +static const char * const LMTP_HELP_TEXT[] = { +/* LMTP_STRT */ + "214-This is DBMail-LMTP.\r\n" + "214-The following commands are supported:\r\n" + "214-LHLO, RSET, NOOP, QUIT, HELP.\r\n" + "214-VRFY, EXPN, MAIL, RCPT, DATA.\r\n" + "214-For more information about a command:\r\n" + "214 Use HELP <command>.\r\n" +/* LMTP_LHLO */ , + "214-The LHLO command begins a client/server\r\n" + "214-dialogue. The commands MAIL, RCPT and DATA\r\n" + "214-may only be issued after a successful LHLO.\r\n" + "214 Syntax: LHLO [your hostname]\r\n" +/* LMTP_DATA */ , + "214-The LHLO command begins a client/server\r\n" + "214-dialogue. The commands MAIL, RCPT and DATA\r\n" + "214-may only be issued after a successful LHLO.\r\n" + "214 Syntax: LHLO [your hostname]\r\n" +/* LMTP_RSET */ , + "214-The LHLO command begins a client/server\r\n" + "214-dialogue. The commands MAIL, RCPT and DATA\r\n" + "214-may only be issued after a successful LHLO.\r\n" + "214 Syntax: LHLO [your hostname]\r\n" +/* LMTP_QUIT */ , + "214-The LHLO command begins a client/server\r\n" + "214-dialogue. The commands MAIL, RCPT and DATA\r\n" + "214-may only be issued after a successful LHLO.\r\n" + "214 Syntax: LHLO [your hostname]\r\n" +/* LMTP_NOOP */ , + "214-The LHLO command begins a client/server\r\n" + "214-dialogue. The commands MAIL, RCPT and DATA\r\n" + "214-may only be issued after a successful LHLO.\r\n" + "214 Syntax: LHLO [your hostname]\r\n" +/* LMTP_HELP */ , + "214-The LHLO command begins a client/server\r\n" + "214-dialogue. The commands MAIL, RCPT and DATA\r\n" + "214-may only be issued after a successful LHLO.\r\n" + "214 Syntax: LHLO [your hostname]\r\n" +/* For good measure. */ , + NULL +}; + +#endif diff --git a/lmtpd.c b/lmtpd.c new file mode 100644 index 00000000..dc1bd8ba --- /dev/null +++ b/lmtpd.c @@ -0,0 +1,314 @@ +/* $Id$ +* (c) 2000-2002 IC&S, The Netherlands +* +* lmtpd.c +* +* main prg for lmtp daemon +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> +#include <errno.h> +#include "imap4.h" +#include "server.h" +#include "debug.h" +#include "misc.h" +#include "dbmail.h" +#include "clientinfo.h" +#include "lmtp.h" +#ifdef PROC_TITLES +#include "proctitleutils.h" +#endif + +#define PNAME "dbmail/lmtpd" + +/* server timeout error */ +#define LMTP_TIMEOUT_MSG "221 Connection timeout BYE" + +struct list smtpItems; +struct list lmtpItems; +struct list sysItems; + +char *configFile = DEFAULT_CONFIG_FILE; + +/* set up database login data */ +extern db_param_t _db_params; + +void SetConfigItems(serverConfig_t *config, struct list *items); +void Daemonize(void); +int SetMainSigHandler(void); +void MainSigHandler(int sig, siginfo_t *info, void *data); + +int lmtp_before_smtp = 0; +int mainRestart = 0; +int mainStop = 0; + +PopSession_t session; +char *myhostname; +char *timeout_setting; + +#ifdef PROC_TITLES +int main(int argc, char *argv[], char **envp) +#else + int main(int argc, char *argv[]) +#endif +{ + serverConfig_t config; + int result, status; + pid_t pid; + + openlog(PNAME, LOG_PID, LOG_MAIL); + + if (argc >= 2 && (argv[1])) + { + if (strcmp (argv[1],"-v") == 0) + { + printf ("\n*** DBMAIL: dbmail-lmtpd version $Revision$ %s\n\n",COPYRIGHT); + return 0; + } + else + if (strcmp(argv[1],"-f")==0 && (argv[2])) + configFile = argv[2]; + } + + SetMainSigHandler(); + Daemonize(); + result = 0; + + do + { + mainStop = 0; + mainRestart = 0; + + trace(TRACE_DEBUG, "main(): reading config"); +#ifdef PROC_TITLES + init_set_proc_title(argc, argv, envp, PNAME); + set_proc_title("%s", "Idle"); +#endif + + /* We need smtp config for bounce.c and forward.c */ + ReadConfig("SMTP", configFile, &smtpItems); + ReadConfig("LMTP", configFile, &lmtpItems); + ReadConfig("DBMAIL", configFile, &sysItems); + SetConfigItems(&config, &lmtpItems); + SetTraceLevel(&lmtpItems); + GetDBParams(&_db_params, &sysItems); + + config.ClientHandler = lmtp_handle_connection; + config.timeoutMsg = LMTP_TIMEOUT_MSG; + + CreateSocket(&config); + trace(TRACE_DEBUG, "main(): socket created, starting server"); + + switch ( (pid = fork()) ) + { + case -1: + close(config.listenSocket); + trace(TRACE_FATAL, "main(): fork failed [%s]", strerror(errno)); + + case 0: + /* child process */ + drop_privileges(config.serverUser, config.serverGroup); + result = StartServer(&config); + + trace(TRACE_INFO, "main(): server done, exit."); + exit(result); + + default: + /* parent process, wait for child to exit */ + while (waitpid(pid, &status, WNOHANG|WUNTRACED) == 0) + { + if (mainStop) + kill(pid, SIGTERM); + + if (mainRestart) + kill(pid, SIGHUP); + + sleep(2); + } + + if (WIFEXITED(status)) + { + /* child process terminated neatly */ + result = WEXITSTATUS(status); + trace(TRACE_DEBUG, "main(): server has exited, exit status [%d]", result); + } + else + { + /* child stopped or signaled, don't like */ + /* make sure it is dead */ + trace(TRACE_DEBUG, "main(): server has not exited normally. Killing.."); + + kill(pid, SIGKILL); + result = 0; + } + } + + list_freelist(&smtpItems.start); + list_freelist(&lmtpItems.start); + list_freelist(&sysItems.start); + close(config.listenSocket); + + } while (result == 1 && !mainStop) ; /* 1 means reread-config and restart */ + + trace(TRACE_INFO, "main(): exit"); + return 0; +} + + +void MainSigHandler(int sig, siginfo_t *info, void *data) +{ + trace(TRACE_DEBUG, "MainSigHandler(): got signal [%d]", sig); + + if (sig == SIGHUP) + mainRestart = 1; + else + mainStop = 1; +} + + +void Daemonize() +{ + if (fork()) + exit(0); + setsid(); + + if (fork()) + exit(0); +} + + +int SetMainSigHandler() +{ + struct sigaction act; + + /* init & install signal handlers */ + memset(&act, 0, sizeof(act)); + + act.sa_sigaction = MainSigHandler; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_SIGINFO; + + sigaction(SIGINT, &act, 0); + sigaction(SIGQUIT, &act, 0); + sigaction(SIGTERM, &act, 0); + sigaction(SIGHUP, &act, 0); + + return 0; +} + + +void SetConfigItems(serverConfig_t *config, struct list *items) +{ + field_t val; + + /* read items: NCHILDREN */ + GetConfigValue("NCHILDREN", items, val); + if (strlen(val) == 0) + trace(TRACE_FATAL, "SetConfigItems(): no value for NCHILDREN in config file"); + + if ( (config->nChildren = atoi(val)) <= 0) + trace(TRACE_FATAL, "SetConfigItems(): value for NCHILDREN is invalid: [%d]", config->nChildren); + + trace(TRACE_DEBUG, "SetConfigItems(): server will create [%d] children", config->nChildren); + + + /* read items: MAXCONNECTS */ + GetConfigValue("MAXCONNECTS", items, val); + if (strlen(val) == 0) + trace(TRACE_FATAL, "SetConfigItems(): no value for MAXCONNECTS in config file"); + + if ( (config->childMaxConnect = atoi(val)) <= 0) + trace(TRACE_FATAL, "SetConfigItems(): value for MAXCONNECTS is invalid: [%d]", config->childMaxConnect); + + trace(TRACE_DEBUG, "SetConfigItems(): children will make max. [%d] connections", config->childMaxConnect); + + + /* read items: TIMEOUT */ + GetConfigValue("TIMEOUT", items, val); + if (strlen(val) == 0) + { + trace(TRACE_DEBUG, "SetConfigItems(): no value for TIMEOUT in config file"); + config->timeout = 0; + } + else if ( (config->timeout = atoi(val)) <= 30) + trace(TRACE_FATAL, "SetConfigItems(): value for TIMEOUT is invalid: [%d]", config->timeout); + + trace(TRACE_DEBUG, "SetConfigItems(): timeout [%d] seconds", config->timeout); + + + /* read items: PORT */ + GetConfigValue("PORT", items, val); + if (strlen(val) == 0) + trace(TRACE_FATAL, "SetConfigItems(): no value for PORT in config file"); + + if ( (config->port = atoi(val)) <= 0) + trace(TRACE_FATAL, "SetConfigItems(): value for PORT is invalid: [%d]", config->port); + + trace(TRACE_DEBUG, "SetConfigItems(): binding to PORT [%d]", config->port); + + + /* read items: BINDIP */ + GetConfigValue("BINDIP", items, val); + if (strlen(val) == 0) + trace(TRACE_FATAL, "SetConfigItems(): no value for BINDIP in config file"); + + strncpy(config->ip, val, IPLEN); + config->ip[IPLEN-1] = '\0'; + + trace(TRACE_DEBUG, "SetConfigItems(): binding to IP [%s]", config->ip); + + + /* read items: RESOLVE_IP */ + GetConfigValue("RESOLVE_IP", items, val); + if (strlen(val) == 0) + trace(TRACE_DEBUG, "SetConfigItems(): no value for RESOLVE_IP in config file"); + + config->resolveIP = (strcasecmp(val, "yes") == 0); + + trace(TRACE_DEBUG, "SetConfigItems(): %sresolving client IP", config->resolveIP ? "" : "not "); + + + /* read items: IMAP-BEFORE-SMTP */ + GetConfigValue("LMTP_BEFORE_SMTP", items, val); + if (strlen(val) == 0) + trace(TRACE_DEBUG, "SetConfigItems(): no value for LMTP_BEFORE_SMTP in config file"); + + lmtp_before_smtp = (strcasecmp(val, "yes") == 0); + + trace(TRACE_DEBUG, "SetConfigItems(): %s LMTP-before-SMTP", + lmtp_before_smtp ? "Enabling" : "Disabling"); + + + /* read items: EFFECTIVE-USER */ + GetConfigValue("EFFECTIVE_USER", items, val); + if (strlen(val) == 0) + trace(TRACE_FATAL, "SetConfigItems(): no value for EFFECTIVE_USER in config file"); + + strncpy(config->serverUser, val, FIELDSIZE); + config->serverUser[FIELDSIZE-1] = '\0'; + + trace(TRACE_DEBUG, "SetConfigItems(): effective user shall be [%s]", config->serverUser); + + + /* read items: EFFECTIVE-GROUP */ + GetConfigValue("EFFECTIVE_GROUP", items, val); + if (strlen(val) == 0) + trace(TRACE_FATAL, "SetConfigItems(): no value for EFFECTIVE_GROUP in config file"); + + strncpy(config->serverGroup, val, FIELDSIZE); + config->serverGroup[FIELDSIZE-1] = '\0'; + + trace(TRACE_DEBUG, "SetConfigItems(): effective group shall be [%s]", config->serverGroup); + + + +} diff --git a/sievecmd.c b/sievecmd.c new file mode 100644 index 00000000..a46aa758 --- /dev/null +++ b/sievecmd.c @@ -0,0 +1,371 @@ +/* + Copyright (C) 2003 Aaron Stone + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later + version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* $Id$ + * This is dbmail-sievecmd, which provides + * a command line interface to the sievescripts */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sievecmd.h" + +#include "auth.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "dbmail.h" +#include "list.h" +#include "debug.h" +#include "db.h" +#include <time.h> +#include <stdarg.h> +#include <sys/types.h> +#include <unistd.h> +#ifdef HAVE_CRYPT_H +#include <crypt.h> +#endif +#include "dbmd5.h" + +char *configFile = DEFAULT_CONFIG_FILE; + +/* set up database login data */ +extern db_param_t _db_params; + +int main(int argc, char *argv[]) +{ + struct list sysItems; + int res = 0, opt = 0, act = 0; + u64_t user_idnr = 0; + char *user_name = NULL; + char *name = NULL; + FILE *source = NULL; + extern char *optarg; + + openlog(PNAME, LOG_PID, LOG_MAIL); + + setvbuf(stdout, 0, _IONBF, 0); + + ReadConfig("DBMAIL", configFile, &sysItems); + SetTraceLevel(&sysItems); + GetDBParams(&_db_params, &sysItems); + + configure_debug(TRACE_ERROR, 1, 0); + + while (opt != -1 && act != 'h') + { + opt = getopt(argc, argv, "a:d:i:r:u:l"); + + switch (opt) + { + case -1: + /* Break right away if this is the end of the args */ + break; + case 'a': + case 'd': + case 'i': + case 'r': + if (act != 0) + act = 'h'; + else + act = opt; + name = optarg; + source = stdin; // FIXME to take files as input, too + break; + case 'u': + user_name = strdup(optarg); + break; + case 'l': + if (act != 0) + act = 'h'; + else + act = opt; + break; + default: + act = 'h'; + break; + } + } + + if (act != 'h' && act != 0) + { + printf ("*** dbmail-sievecmd ***\n"); + + /* Open database connection */ + printf ("Opening connection to database...\n"); + if (db_connect() != 0) + { + printf ("Failed. Could not connect to database (check log)\n"); + return -1; + } + + /* Open authentication connection */ + printf ("Opening connection to authentication...\n"); + if (auth_connect() != 0) + { + printf ("Failed. Could not connect to authentication (check log)\n"); + return -1; + } + + printf ("Ok. Connected!\n"); + + /* Retrieve the user ID number */ + switch(auth_user_exists(user_name, &user_idnr)) + { + case 0: + printf( "User [%s] does not exist!\n", user_name); + break; + case -1: + printf( "Error retrieving User ID Number\n" ); + res = -1; + goto mainend; + } + } + + switch (act) + { + case 'a': + res = do_activate(user_idnr, name); + break; + case 'd': + res = do_deactivate(user_idnr, name); + break; + case 'i': + res = do_insert(user_idnr, name, source); + break; + case 'r': + res = do_remove(user_idnr, name); + break; + case 'l': + res = do_list(user_idnr); + break; + case 'h': + default: + res = do_showhelp(); + break; + } + + mainend: + free(user_name); + db_disconnect(); + auth_disconnect(); + return res; +} + + +int do_activate(u64_t user_idnr, char *name) +{ + int res = 0; + + res = db_activate_sievescript(user_idnr, name); + if (res == -3) + { + printf( "Script [%s] does not exist.\n", name ); + return -1; + } + else if (res != 0) + { + printf( "Error activating script [%s].\n" + "It is possible that no script is currently active!\n", name ); + return -1; + } + printf( "Script [%s] is now active. All others are inactive.\n", name ); + + return 0; +} + + +int do_deactivate(u64_t user_idnr, char *name) +{ + int res = 0; + + res = db_deactivate_sievescript(user_idnr, name); + if (res == -3) + { + printf( "Script [%s] does not exist.\n", name ); + return -1; + } + else if (res != 0) + { + printf( "Error deactivating script [%s].\n", name ); + return -1; + } + printf( "Script [%s] is now deactivated. No scripts are currently active.\n", name ); + + return 0; +} + + +int do_insert(u64_t user_idnr, char *name, FILE *source) +{ + int res = 0; + char *buf = NULL; + char *errmsg = NULL; + + /* Read the file into a char array */ + res = read_script_file(source, &buf); + if (res != 0) + { + printf( "Error reading in your script!\n" ); + return -1; + } + + /* Check if the script is valid */ + res = my_sieve_script_validate(buf, &errmsg); + if (res != 0) + { + printf( "Script has errors: [%s].\n", name, errmsg ); + return -1; + } + + /* Make the DB call to store the script */ + res = db_add_sievescript(user_idnr, name, buf); + if (res == -3) + { + printf( "Script [%s] already exists.\n", name ); + return -1; + } + else if (res != 0) + { + printf( "Error inserting script [%s] into the database!\n", name ); + return -1; + } + + printf( "Script [%s] successfully inserted and marked inactive!\n", name ); + return 0; +} + + +int do_remove(u64_t user_idnr, char *name) +{ + int res; + + res = db_delete_sievescript(user_idnr, name); + if (res == -3) + { + printf( "Script [%s] does not exist.\n", name ); + return -1; + } + else if (res != 0) + { + printf( "Error deleting script [%s].\n", name ); + return -1; + } + + printf( "Script [%s] deleted.\n", name ); + + return 0; +} + + +int do_list(u64_t user_idnr) +{ + struct list scriptlist; + struct element *tmp; + + if(db_get_sievescript_listall(user_idnr, &scriptlist) < 0) + { + printf("Error retrieving Sieve script list.\n"); + return -1; + } + + if (list_totalnodes(&scriptlist) > 0) + printf( "Found %ld scripts:\n", list_totalnodes(&scriptlist) ); + else + printf( "No scripts found!\n" ); + + tmp = list_getstart(&scriptlist); + while (tmp) + { + struct ssinfo *info = (struct ssinfo *)tmp->data; + if(info->active == 1) + printf(" + "); + else + printf(" - "); + printf("%s\n", info->name); + tmp = tmp->nextnode; + } + + if (scriptlist.start) + list_freelist(&scriptlist.start); + + return 0; +} + + +int do_showhelp() +{ + printf ("*** dbmail-sievecmd ***\n"); + + printf("Use this program to manage your users' Sieve scripts.\n"); + printf("See the man page for more info. Summary:\n\n"); + printf(" -u username Username of script user \n"); + printf(" -l List scripts belonging to user \n"); + printf(" -a scriptname Activate the named script \n"); + printf(" (only one script can be active; \n" + " deactivates any others) \n"); + printf(" -d scriptname Deactivate the named script \n"); + printf(" (no scripts will be active after this) \n"); + printf(" -i scriptname file Insert the named script from file \n"); + printf(" (a single dash, -, indicates input \n" + " from STDIN) \n"); + printf(" -r scriptname Remove the named script \n"); + printf(" (if script was active, no script is \n" + " active after deletion) \n"); + + return 0; +} + + +int read_script_file(FILE *f, char **m_buf) +{ + size_t f_len=0; + size_t f_pos=0; + char *tmp_buf = NULL; + char *f_buf = NULL; + + if (!f) + { + printf( "Received NULL as script input\n" ); + return -1; + } + + while(!feof(f)) + { + if( f_pos + 1 >= f_len ) + { + tmp_buf = realloc(f_buf, sizeof(char) * (f_len+=200)); + if( tmp_buf != NULL ) + f_buf = tmp_buf; + else + return -2; + } + f_buf[f_pos] = fgetc(f); + f_pos++; + } + + if(f_pos) + f_buf[f_pos] = '\0'; + + /* Since f_buf is either NULL or valid, we're golden */ + *m_buf = f_buf; + return 0; +} + diff --git a/sievecmd.h b/sievecmd.h new file mode 100644 index 00000000..7fa4e5de --- /dev/null +++ b/sievecmd.h @@ -0,0 +1,39 @@ +/* + Copyright (C) 2003 Aaron Stone + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later + version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include "dbmailtypes.h" + +#define PNAME "dbmail/sievecmd" + +int do_showhelp(void); +int do_list(u64_t user_idnr); +int do_activate(u64_t user_idnr, char *name); +int do_deactivate(u64_t user_idnr, char *name); +int do_remove(u64_t user_idnr, char *name); +int do_insert(u64_t user_idnr, char *name, FILE *source); + +int read_script_file(FILE *f, char **m_buf); + @@ -0,0 +1,33 @@ +/* $Id$ + * (c) 2003 Aaron Stone + * + * Headers for sorting.c */ + +#ifndef _SORTING_H +#define _SORTING_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <stdlib.h> +#include "debug.h" +#include "dbmailtypes.h" + +#define SA_KEEP 1 +#define SA_DISCARD 2 +#define SA_REDIRECT 3 +#define SA_REJECT 4 +#define SA_FILEINTO 5 +#define SA_SIEVE 6 + +typedef struct sort_action { + int method; + char *destination; + char *message; +} sort_action_t; + +int sort_and_deliver(u64_t msgidnr, char *header, u64_t headersize, u64_t msgsize, u64_t useridnr, char *mailbox); + +#endif /* #ifndef _SORTING_H */ diff --git a/sort/Makefile.am b/sort/Makefile.am new file mode 100644 index 00000000..7f6e3045 --- /dev/null +++ b/sort/Makefile.am @@ -0,0 +1,25 @@ +# Copyright (C) 1999-2003 IC & S dbmail@ic-s.nl +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later +# version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +lib_LIBRARIES = libsortdbmail.a + +if SIEVE +libsortdbmail_a_SOURCES = sort.c sortsieve.c +CFLAGS += -DSIEVE +else +libsortdbmail_a_SOURCES = sort.c +endif diff --git a/sort/sort.c b/sort/sort.c new file mode 100644 index 00000000..cb038710 --- /dev/null +++ b/sort/sort.c @@ -0,0 +1,337 @@ +/* $Id$ + * (c) 2003 Aaron Stone + * + * Functions for running user defined sorting rules + * on a message in the temporary store, usually + * just delivering the message to the user's INBOX + * ...unless they have fancy rules defined, that is :-) + * */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <time.h> +#include <ctype.h> +#include "db.h" +#include "auth.h" +#include "debug.h" +#include "list.h" +#include "pipe.h" +#include "bounce.h" +#include "forward.h" +#include "sort.h" +#include "dbmail.h" +#include "debug.h" +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <unistd.h> +#include "dbmd5.h" +#include "misc.h" + +#ifdef SIEVE +#include "sortsieve.h" +#endif + +extern struct list smtpItems, sysItems; + +/* Run the user's sorting rules on this message + * Retrieve the action list as either + * a linked list of things to do, or a + * single thing to do. Not sure yet... + * + * Then do it! + * */ +int sort_and_deliver(u64_t msgidnr, char *header, u64_t headersize, u64_t msgsize, u64_t useridnr, char *mailbox) +{ + field_t val; + int do_regex = 0, do_sieve = 0; + struct list actions; + struct element *tmp; + int actiontaken = 0, ret = 0; + u64_t mboxidnr, newmsgidnr; + char unique_id[UID_SIZE]; + char *bounce_id = NULL; + char *inbox = "INBOX"; + + + GetConfigValue("SQLREGEX", &smtpItems, val); + if (strcasecmp(val, "yes") == 0) + do_regex = 1; + + GetConfigValue("LIBSIEVE", &smtpItems, val); + if (strcasecmp(val, "yes") == 0) + do_sieve = 1; + + list_init(&actions); + + if (do_regex) + { + /* Call out to Jonas' regex sorting function! + * */ + // ret = db_regexsort(useridnr, header, actions); + trace(TRACE_ERROR, "%s, %s: Regex sort is enabled in dbmail.conf, but has not been compiled"); + } + + if (do_sieve) + { + /* Don't code the Sieve guts right here, + * call out to a function that encapsulates it! + * */ +#ifdef SIEVE + ret = sortsieve_msgsort(useridnr, header, headersize, msgsize, &actions); +#else + /* Give the postmaster a clue as to why Sieve isn't working... */ + trace(TRACE_ERROR, "%s, %s: Sieve enabled in dbmail.conf, but Sieve support has not been compiled"); +#endif /* SIEVE */ + } + + if (mailbox == NULL) + mailbox = inbox; + + /* actions is a list of things to do with this message + * each data pointer in the actions list references + * a structure like this: + * + * typedef sort_action { + * int method, + * char *destination, + * char *message + * } sort_action_t; + * + * Where message is some descriptive text, used + * primarily for rejection noticed, and where + * destination is either a mailbox name or a + * forwarding address, and method is one of these: + * + * SA_KEEP, + * SA_DISCARD, + * SA_REDIRECT, + * SA_REJECT, + * SA_FILEINTO + * (see RFC 3028 [SIEVE] for details) + * + * SA_SIEVE: + * In addition, this implementation allows for + * the internel Regex matching to call a Sieve + * script into action. In this case, the method + * is SA_SIEVE and the destination is the script's name. + * Note that Sieve must be enabled in the configuration + * file or else an error will be generated. + * + * In the absence of any valid actions (ie. actions + * is an empty list, or all attempts at performing the + * actions fail...) an implicit SA_KEEP is performed, + * using INBOX as the destination (hardcoded). + * */ + + if (list_totalnodes(&actions) > 0) + { + tmp = list_getstart(&actions); + while (tmp != NULL) + { + /* Try not to think about the structures too hard ;-) */ + switch ( (int)((sort_action_t *)tmp->data)->method ) + { + case SA_SIEVE: + { + /* Run the script specified by destination and + * add the resulting list onto the *end* of the + * actions list. Note that this is a deep hack... + * */ + if ((char *)((sort_action_t *)tmp->data)->destination != NULL) + { + struct list localtmplist; + struct element *localtmpelem; +// if (sortsieve_msgsort(useridnr, header, headersize, (char *)((sort_action_t *)tmp->data)->destination, localtmplist)) + { + /* FIXME: This can all be replaced with some + * function called list_append(), if written! */ + /* Fast forward to the end of the actions list */ + localtmpelem = list_getstart(&actions); + while (localtmpelem != NULL) + { + localtmpelem = localtmpelem->nextnode; + } + /* And tack on the start of the Sieve list */ + localtmpelem->nextnode = list_getstart(&localtmplist); + /* Remeber to increment the node count, too */ + actions.total_nodes += list_totalnodes(&localtmplist); + } + } + break; + } + case SA_FILEINTO: + { + char *fileinto_mailbox = (char *)((sort_action_t *)tmp->data)->destination; + + /* If the action doesn't come with a mailbox, use the default. */ + + if (fileinto_mailbox == NULL) + { + fileinto_mailbox = mailbox; + trace(TRACE_MESSAGE, "sort_and_deliver(): mailbox not specified, using [%s]", + fileinto_mailbox); + } + + + /* Did we fail to create the mailbox? */ + if (db_find_create_mailbox(mailbox, useridnr, &mboxidnr) != 0) + { + /* FIXME: Serious failure situation! This needs to be + * passed up the chain to notify the user, sender, etc. + * Perhaps we should *force* the implicit-keep to occur, + * or give another try at using INBOX. */ + trace(TRACE_ERROR, "sort_and_deliver(): mailbox [%s] not found nor created, message may not have been delivered", + fileinto_mailbox); + } + else + { + switch(db_copymsg(msgidnr, mboxidnr, useridnr, &newmsgidnr)) + { + case -2: + /* Couldn't deliver because the quota has been reached */ + bounce_id = auth_get_userid(&useridnr); + bounce(header, bounce_id, BOUNCE_STORAGE_LIMIT_REACHED); + my_free (bounce_id); + break; + case -1: + /* Couldn't deliver because something something went wrong */ + trace(TRACE_ERROR, "sort_and_deliver(): error copying message to user [%llu]", useridnr); + /* Don't worry about error conditions. + * It's annoying if the message isn't delivered, + * but as long as *something* happens it's OK. + * Otherwise, actiontaken will be 0 and another + * delivery attempt will be made before passing + * up the error at the end of the function. + * */ + break; + default: + trace(TRACE_MESSAGE, "sort_and_deliver(): message id=%llu, size=%llu is inserted", + newmsgidnr, msgsize+headersize); + + /* Create a unique ID for this message; + * Each message for each user must have a unique ID! + * */ + create_unique_id(unique_id, newmsgidnr); + db_update_message(newmsgidnr, unique_id, msgsize+headersize, 0); + + actiontaken = 1; + break; + } + } + + /* If these are not same pointers, then we need to free. */ + if (fileinto_mailbox != mailbox) + my_free(fileinto_mailbox); + + break; + } + case SA_DISCARD: + { + /* Basically do nothing! */ + actiontaken = 1; + break; + } + case SA_REJECT: + { + // FIXME: I'm happy with this code, but it's not quite right... + // Plus we want to specify a message to go along with it! + bounce_id = auth_get_userid(&useridnr); + bounce(header, bounce_id, BOUNCE_NO_SUCH_USER); + + my_free(bounce_id); + actiontaken = 1; + break; + } + case SA_REDIRECT: + { + char *forward_id; + struct list targets; + + list_init(&targets); + list_nodeadd(&targets, (char *)((sort_action_t *)tmp->data)->destination, strlen((char *)((sort_action_t *)tmp->data)->destination)+1); + my_free((char *)((sort_action_t *)tmp->data)->destination); + + /* Put the destination into the targets list */ + /* The From header will contain... */ + forward_id = auth_get_userid(&useridnr); + forward(msgidnr, &targets, forward_id, header, headersize); + + list_freelist(&targets.start); + my_free (forward_id); + actiontaken = 1; + break; + } + /* + case SA_KEEP: + default: + { + // Don't worry! This is handled by implicit keep :-) + break; + } + */ + } /* case */ + tmp = tmp->nextnode; + } /* while */ + list_freelist(&actions.start); + } /* if */ + else + { + /* Might as well be explicit about this... */ + actiontaken = 0; + } + + /* This is that implicit keep I mentioned earlier... + * If possible, put the message in the specified + * mailbox, otherwise use INBOX. */ + if (actiontaken == 0) + { + /* Did we fail to create the mailbox? */ + if (db_find_create_mailbox(mailbox, useridnr, &mboxidnr) != 0) + { + /* Serious failure situation! */ + trace(TRACE_ERROR, "sort_and_deliver(): INBOX not found"); + } + else + { + switch(db_copymsg(msgidnr, mboxidnr, useridnr, &newmsgidnr)) + { + case -2: + /* Couldn't deliver because the quota has been reached */ + bounce_id = auth_get_userid(&useridnr); + bounce(header, bounce_id, BOUNCE_STORAGE_LIMIT_REACHED); + my_free(bounce_id); + trace(TRACE_DEBUG, "%s, %s: error copying message to user [%llu], maxmail exceeded", + __FILE__, __FUNCTION__, useridnr); + break; + case -1: + /* Couldn't deliver because something something went wrong */ + trace(TRACE_ERROR, "%s, %s: error copying message to user [%llu]", + __FILE__, __FUNCTION__, useridnr); + /* FIXME: We need a way to pass this error back out + * so that, for example, the LMTP server can tell the + * MTA that it wasn't able to deliver successfully! */ + break; + default: + trace(TRACE_MESSAGE,"%s, %s: message id=%llu, size=%llu is inserted", + __FILE__, __FUNCTION__, newmsgidnr, msgsize+headersize); + + /* Create a unique ID for this message; + * Each message for each user must have a unique ID! + * */ + create_unique_id(unique_id, newmsgidnr); + db_update_message(newmsgidnr, unique_id, msgsize+headersize, 0); + + actiontaken = 1; + break; + } + } + } + + return actiontaken; +} + diff --git a/sort/sortsieve.c b/sort/sortsieve.c new file mode 100644 index 00000000..aa2fe424 --- /dev/null +++ b/sort/sortsieve.c @@ -0,0 +1,322 @@ +/* $Id$ + * (c) 2003 Aaron Stone + * + * Functions for running user defined sorting rules + * on a message in the temporary store, usually + * just delivering the message to the user's INBOX + * ...unless they have fancy rules defined, that is :-) + * */ + + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <time.h> +#include <ctype.h> +#include "db.h" +#include "auth.h" +#include "debug.h" +#include "list.h" +#include "dbmail.h" +#include "debug.h" +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <unistd.h> +#include "dbmd5.h" +#include "misc.h" + +#include "sortsieve.h" +#include "sort.h" +#include <sieve2_interface.h> + +extern struct list smtpItems, sysItems; + +/* typedef sort_action { + * int method, + * char *destination, + * char *message + * } sort_action_t; + * */ + +/* Pull up the relevant sieve scripts for this + * user and begin running them against the header + * and possibly the body of the message. + * + * Returns 0 on success, -1 on failure, + * and +1 on success but with memory leaking. + * In the +1 case, if called from a daemon + * such as dbmail-lmtpd, the daemon should + * finish storing the message and restart. + * */ +int sortsieve_msgsort(u64_t useridnr, char *header, u64_t headersize, u64_t messagesize, struct list *actions) +{ + sieve2_message_t *m; + sieve2_support_t *p; + sieve2_script_t *s; + sieve2_action_t *a; + sieve2_loader_t scriptloader, msgloader; + char *scriptname = NULL, *script = NULL, *freestr = NULL; + int res = 0, ret = 0; + + /* Pass the address of the char *script, and it will come + * back magically allocated. Don't forget to free it later! */ + res = db_get_sievescript_active(useridnr, &scriptname); + if( res < 0 ) { + printf("db_get_sievescript_active() returns %d\n", res); + ret = -1; + goto no_free; + } + + printf( "Looking up script [%s]\n", scriptname ); + res = db_get_sievescript_byname(useridnr, scriptname, &script); + if( res < 0 ) { + printf("db_get_sievescript_byname() returns %d\n", res); + ret = -1; + goto char_scriptname_free; + } + + res = sieve2_action_alloc(&a); + if (res != SIEVE2_OK) { + printf("sieve2_action_alloc() returns %d\n", res); + ret = -1; + goto char_script_free; + } + + res = sieve2_support_alloc(&p); + if (res != SIEVE2_OK) { + printf("sieve2_support_alloc() returns %d\n", res); + ret = -1; + goto action_free; + } + + res = sieve2_support_register(p, SIEVE2_ACTION_FILEINTO); + res = sieve2_support_register(p, SIEVE2_ACTION_REDIRECT); + res = sieve2_support_register(p, SIEVE2_ACTION_REJECT); +// res = sieve2_support_register(p, SIEVE2_ACTION_NOTIFY); + + res = sieve2_script_alloc(&s); + if (res != SIEVE2_OK) { + printf("sieve2_script_alloc() returns %d\n", res); + ret = -1; + goto support_free; + } + + res = sieve2_support_bind(p, s); + if (res != SIEVE2_OK) { + printf("sieve2_support_bind() returns %d\n", res); + ret = -1; + goto script_free; + } + + res = sieve2_script_parse(s, script); + if (res != SIEVE2_OK) { + printf("sieve2_script_parse() returns %d: %s\n", res, sieve2_errstr(res, &freestr)); + my_free(freestr); + ret = -1; + goto script_free; + } + + res = sieve2_message_alloc(&m); + if (res != SIEVE2_OK) { + printf("sieve2_message_alloc() returns %d\n", res); + ret = -1; + goto script_free; + } + + res = sieve2_message_register(m, &messagesize, SIEVE2_MESSAGE_SIZE); + if (res != SIEVE2_OK) { + printf("sieve2_message_register() returns %d\n", res); + ret = -1; + goto message_free; + } + res = sieve2_message_register(m, header, SIEVE2_MESSAGE_HEADER); + if (res != SIEVE2_OK) { + printf("sieve2_message_register() returns %d\n", res); + ret = -1; + goto message_free; + } + + res = sieve2_script_exec(s, m, a); + if (res != SIEVE2_OK) { + printf("sieve2_execute_script() returns %d\n", res); + ret = -1; + goto message_free; + } + + res = sortsieve_unroll_action(a, actions); + if (res != SIEVE2_OK && res != SIEVE2_DONE ) { + printf("unroll_action() returns %d\n", res); + ret = -1; + goto action_free; + } + +message_free: + res = sieve2_message_free(m); + if (res != SIEVE2_OK) { + printf("sieve2_message_free() returns %d\n", res); + ret = 1; + } + +script_free: + res = sieve2_script_free(s); + if (res != SIEVE2_OK) { + printf("sieve2_script_free() returns %d\n", res); + ret = 1; + } + +support_free: + res = sieve2_support_free(p); + if (res != SIEVE2_OK) { + printf("sieve2_support_free() returns %d\n", res); + ret = 1; + } + +action_free: + res = sieve2_action_free(a); + if (res != SIEVE2_OK) { + printf("sieve2_action_free() returns %d\n", res); + ret = 1; + } + + /* Good thing we're not forgetting ;-) */ +char_script_free: + if (script != NULL) + my_free(script); +char_scriptname_free: + if (scriptname != NULL) + my_free(scriptname); + +no_free: + return ret; +} + +int sortsieve_unroll_action(sieve2_action_t *a, struct list *actions) +{ + int res = SIEVE2_OK; + int code; + void *action_context; + + /* Working variables to set up + * the struct then nodeadd it */ + sort_action_t *tmpsa = NULL; + char *tmpdest = NULL; + char *tmpmsg = NULL; + int tmpmeth = 0; + + while(res == SIEVE2_OK) + { + if((tmpsa = malloc(sizeof(sort_action_t))) == NULL) + break; + res = sieve2_action_next(&a, &code, &action_context); + if(res == SIEVE2_DONE) + { + printf("We've reached the end.\n"); + break; + } + else if (res != SIEVE2_OK) + { + printf("Error in action list.\n"); + break; + } + printf("Action code is: %d\n", code); + + switch (code) + { + case SIEVE2_ACTION_REDIRECT: + { + sieve2_redirect_context_t *context = (sieve2_redirect_context_t *)action_context; + printf( "Action is REDIRECT: " ); + printf( "Destination is %s\n", context->addr); + tmpmeth = SA_REDIRECT; + tmpdest = strdup(context->addr); + break; + } + case SIEVE2_ACTION_REJECT: + { + sieve2_reject_context_t *context = (sieve2_reject_context_t *)action_context; + printf( "Action is REJECT: " ); + printf( "Message is %s\n", context->msg); + tmpmeth = SA_REJECT; + tmpmsg = strdup(context->msg); + break; + } + case SIEVE2_ACTION_DISCARD: + printf( "Action is DISCARD\n" ); + tmpmeth = SA_DISCARD; + break; + case SIEVE2_ACTION_FILEINTO: + { + sieve2_fileinto_context_t *context = (sieve2_fileinto_context_t *)action_context; + printf( "Action is FILEINTO: " ); + printf( "Destination is %s\n", context->mailbox); + tmpmeth = SA_FILEINTO; + tmpdest = strdup(context->mailbox); + break; + } + case SIEVE2_ACTION_NOTIFY: + { + sieve2_notify_context_t *context = (sieve2_notify_context_t *)action_context; + printf( "Action is NOTIFY: \n" ); + // FIXME: Prefer to have a function for this? + while(context != NULL) + { + printf( " ID \"%s\" is %s\n", context->id, ( context->isactive ? "ACTIVE" : "INACTIVE" ) ); + printf( " Method is %s\n", context->method ); + printf( " Priority is %s\n", context->priority ); + printf( " Message is %s\n", context->message ); + if(context->options != NULL) + { + size_t opt = 0; + while(context->options[opt] != NULL) + { + printf( " Options are %s\n", context->options[opt] ); + opt++; + } + } + context = context->next; + } + break; + } + case SIEVE2_ACTION_KEEP: + printf( "Action is KEEP\n" ); + break; + default: + printf( "Unrecognized action code: %d\n", code ); + break; + } /* case */ + + tmpsa->method = tmpmeth; + tmpsa->destination = tmpdest; + tmpsa->message = tmpmsg; + + list_nodeadd(actions, tmpsa, sizeof(sort_action_t)); + + my_free(tmpsa); + tmpsa = NULL; + + } /* while */ + + if (tmpsa != NULL) + my_free(tmpsa); + + return res; +} + +/* Return 0 on script OK, 1 on script error. */ +int sortsieve_script_validate(char *script, char **errmsg) +{ + if(sieve2_validate(t, s, p, e) == SIEVE2_OK) + { + *errmsg = NULL; + return 0; + } + else + { + *errmsg = "Script error..."; + return 1; + } +} + diff --git a/sort/sortsieve.h b/sort/sortsieve.h new file mode 100644 index 00000000..9fb4048e --- /dev/null +++ b/sort/sortsieve.h @@ -0,0 +1,23 @@ +/* $Id$ + * (c) 2003 Aaron Stone + * + * Headers for sieve.c */ + +#ifndef _SIEVE_H +#define _SIEVE_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sort.h" +#include "dbmailtypes.h" +#include <sieve2_interface.h> + +#define MAX_SIEVE_SCRIPTNAME 100 + +int sortsieve_msgsort(u64_t useridnr, char *header, u64_t headersize, u64_t messagesize, struct list *actions); +int sortsieve_unroll_action(sieve2_action_t *a, struct list *actions); +int sortsieve_script_validate(char *script, char **errmsg); + +#endif diff --git a/sql/migrate_singe_user.py b/sql/migrate_singe_user.py new file mode 100755 index 00000000..f649c71f --- /dev/null +++ b/sql/migrate_singe_user.py @@ -0,0 +1,236 @@ +#!/usr/bin/env python + +# dbmail database migration script. Use this script to make the transition +# from DBMail 1.x to DBMail 2.x + +import sys + +print """ +welcome to the DBMail 1.x -> 2.x migration script +************************************************** + + Copyright (C) 2003 IC & S dbmail@ic-s.nl + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later + version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +""" + +print "***********************************************************" +print "These next questions concern your DBMail 1.x database" +print "What Database system does your DBMail 1.x run on? (type a number)" +dbmail_1_x = {} +dbmail_1_x['type'] = input("\t1. MySQL\n\t2. PostgreSQL\n") +dbmail_1_x['host'] = raw_input("Database Host:") +dbmail_1_x['name'] = raw_input("name of database:") +dbmail_1_x['user'] = raw_input("DB user:") +dbmail_1_x['pass'] = raw_input("DB password:") +if dbmail_1_x['host'] == "": + dbmail_1_x['host'] = "localhost" +if dbmail_1_x['name'] == "": + dbmail_1_x['name'] = "dbmail" +if dbmail_1_x['user'] == "": + dbmail_1_x['user'] = "dbmail" +if dbmail_1_x['pass'] == "": + dbmail_1_x['pass'] = "Ldiks89M" + +print "What Database system does your DBMail 2.0 run on? (type a number)" +dbmail_2_0 = {} +dbmail_2_0['type'] = input("\t1. MySQL\n\t2. PostgreSQL\n") +dbmail_2_0['host'] = raw_input("Database Host:") +dbmail_2_0['name'] = raw_input("name of database:") +dbmail_2_0['user'] = raw_input("DB user:") +dbmail_2_0['pass'] = raw_input("DB password:") +if dbmail_2_0['host'] == "": + dbmail_2_0['host'] = "tsunami.fastxs.net" +if dbmail_2_0['name'] == "": + dbmail_2_0['name'] = "dbmail2" +if dbmail_2_0['user'] == "": + dbmail_2_0['user'] = "dbmail" +if dbmail_2_0['pass'] == "": + dbmail_2_0['pass'] = "pass" + +print "\nLOADING DATABASE DRIVERS\n" +if dbmail_1_x['type'] == 1 or dbmail_2_0['type'] == 1: + print "loading MySQL driver" + import MySQLdb +if dbmail_1_x['type'] == 2 or dbmail_2_0['type'] == 2: + print "loading MySQL driver" + import pyPgSQL.PgSQL as PgSQL + +print "connecting to databases" + +if dbmail_1_x['type'] == 1: + conn_1 = MySQLdb.connect(user = dbmail_1_x['user'], db=dbmail_1_x['name'], passwd = dbmail_1_x['pass'], host = dbmail_1_x['host']) +else: + conn_1 = PgSQL.connect(user = dbmail_1_x['user'], database=dbmail_1_x['name'], password = dbmail_1_x['pass'], host = dbmail_1_x['host']) +if dbmail_2_0['type'] == 1: + conn_2 = MySQLdb.connect(user = dbmail_2_0['user'], db=dbmail_2_0['name'], passwd = dbmail_2_0['pass'], host = dbmail_2_0['host']) +else: + conn_2 = PgSQL.connect(user = dbmail_2_0['user'], databaseb=dbmail_2_0['name'], password = dbmail_2_0['pass'], host = dbmail_2_0['host']) +# get two database cursors, one for each database +cursor_1 = conn_1.cursor() +cursor_2 = conn_2.cursor() + +# first find the user_idnr to copy information for +user_name = raw_input("Name of user to copy data for?") + +print "finding user in database" +cursor_1.execute("""SELECT user_idnr, passwd, client_idnr, maxmail_size, encryption_type + FROM users WHERE userid = %s""", + (user_name)) +record = cursor_1.fetchone() +if record == None: + print "no such user found.. exiting" + sys.exit(-1) +orig_user_nr = record[0] + +cursor_2.execute("""INSERT INTO users (userid, passwd, client_idnr, maxmail_size, + encryption_type) VALUES (%s, %s, %s, %s, %s) + """, (user_name, record[1], record[2], record[3], record[4])) +cursor_2.execute("""SELECT MAX(user_idnr) FROM users""") +user_record = cursor_2.fetchone() +if user_record == None: + sys.exit(-1) +new_user_id = user_record[0] + +print new_user_id, "=nieuwe user" + + +# copy all records from the aliases table, this does not copy chained aliases! + +print "copying aliases table" +cursor_1.execute("""SELECT alias, client_idnr + FROM aliases WHERE deliver_to = %s""", (orig_user_nr)) +i = 0 +while 1: + record = cursor_1.fetchone() + if record == None: + break + cursor_2.execute("""INSERT INTO aliases (alias, + deliver_to, client_idnr) VALUES + (%s, %s, %s)""", (record[0], new_user_id, record[1])) + i = i + 1 +print "copied %d records from aliases table" % (i) + +# mailboxes +print "copying mailboxes, messages and messageblocks table" +nr_mailboxes = 0 +cursor_1.execute("""SELECT mailbox_idnr, name, seen_flag, + answered_flag, deleted_flag, flagged_flag, recent_flag, + draft_flag, no_inferiors, no_select, permission, + is_subscribed FROM mailboxes WHERE owner_idnr = %s""", + (orig_user_nr)) +while 1: + mailbox_record = cursor_1.fetchone() + if mailbox_record == None: + break + orig_mailbox_id = mailbox_record[0] + mailbox_name = mailbox_record[1] + cursor_2.execute("""INSERT INTO mailboxes + (owner_idnr, name, seen_flag, + answered_flag, deleted_flag, flagged_flag, recent_flag, + draft_flag, no_inferiors, no_select, permission, + is_subscribed) + VALUES (%s, %s, %s, %s, %s, %s, + %s,%s,%s,%s,%s)""", (new_user_id, mailbox_name, mailbox_record[2], + mailbox_record[3], + mailbox_record[4], mailbox_record[5], + mailbox_record[6], mailbox_record[7], + mailbox_record[8], mailbox_record[9], + mailbox_record[10])) + + cursor_2.execute("""SELECT mailbox_idnr FROM mailboxes WHERE name = %s AND + owner_idnr = %s""", (mailbox_name, new_user_id)) + tmp_record = cursor_2.fetchone() + if tmp_record == None: + break + new_mailbox_id = tmp_record[0] + if mailbox_record[11] == '1': + cursor2.execute("""INSERT INTO subscription (user_id, mailbox_id) VALUES (%s, %s)""", (new_user_id, new_mailbox_id)) + + nr_mailboxes += 1 + print "now get all messages in mailbox %s, with id %s" % (mailbox_name, orig_mailbox_id) + cursor_message_source = conn_1.cursor() + cursor_message_source.execute("""SELECT message_idnr, messagesize, rfcsize, internal_date, seen_flag, + answered_flag, deleted_flag, flagged_flag, + recent_flag, draft_flag, unique_id, status + FROM messages WHERE mailbox_idnr = %s + """, (orig_mailbox_id)) + while 1: + message_record = cursor_message_source.fetchone() + if message_record == None: + break + # first insert physmessage + orig_message_id = message_record[0] + cursor_2.execute("""INSERT into physmessage (messagesize, rfcsize, internal_date) + VALUES (%s, %s, %s)""", (message_record[1], message_record[2], message_record[3])) + cursor_2.execute("""SELECT MAX(id) from physmessage""") + tmp2_record = cursor_2.fetchone() + if tmp2_record == None: + break + physmessage_id = tmp2_record[0] + # now insert the message record + cursor_2.execute("""INSERT INTO messages (mailbox_idnr, physmessage_id, seen_flag, + answered_flag, deleted_flag, flagged_flag, recent_flag, draft_flag, + unique_id, status) VALUES (%s, %s, %s, %s, %s ,%s, + %s, %s, %s, %s)""", (new_mailbox_id, physmessage_id, + message_record[4], message_record[5], + message_record[6], message_record[7], + message_record[8], message_record[9], + message_record[10], message_record[11])) + cursor_2.execute("""SELECT MAX(message_idnr) FROM messages""") + # now get all messageblks for this message + cursor_msgblk_source = conn_1.cursor() + cursor_msgblk_source.execute("""SELECT messageblk, blocksize FROM messageblks + WHERE message_idnr = %s""", (orig_message_id)) + while 1: + block_record = cursor_msgblk_source.fetchone() + if block_record == None: + break + # insert messageblk + cursor_2.execute("""INSERT into messageblks (physmessage_id, messageblk, + blocksize) VALUES (%s, %s, %s) + """, (physmessage_id, block_record[0], block_record[1])) + + cursor_msgblk_source.close() + cursor_message_source.close() + + + +cursor_2.execute("""SELECT SUM(pm.messagesize) FROM mailboxes mbx, + messages msg, physmessage pm + WHERE pm.id = msg.physmessage_id + AND msg.mailbox_idnr = mbx.mailbox_idnr + AND mbx.owner_idnr = %s + AND msg.status < '2'""", (new_user_id)) +size = cursor_2.fetchone()[0] +if size == None: + size = 0L + +print "user %ld, size %ld" % (new_user_id,size) + +cursor_2.execute("""UPDATE users SET curmail_size = %s WHERE + user_idnr = %s""", (size, new_user_id)) + + +# don't forget to update the curmail.. +cursor_1.close() +cursor_2.close() +conn_1.close() +conn_2.close() + + + + diff --git a/timsieve.c b/timsieve.c new file mode 100644 index 00000000..127be50d --- /dev/null +++ b/timsieve.c @@ -0,0 +1,736 @@ +/* $Id$ + * (c) 2000-2002 IC&S, The Netherlands + * + * implementation for tims commands according to RFC 1081 */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "dbmail.h" +#include "sort/sortsieve.h" +#include "timsieve.h" +#include "db.h" +#include "debug.h" +#include "dbmailtypes.h" +#include "auth.h" +#include "misc.h" +#include "clientinfo.h" +#ifdef PROC_TITLES +#include "proctitleutils.h" +#endif + +#include <sieve2_interface.h> + +#define INCOMING_BUFFER_SIZE 512 + +/* default timeout for server daemon */ +#define DEFAULT_SERVER_TIMEOUT 300 + +/* max_errors defines the maximum number of allowed failures */ +#define MAX_ERRORS 3 + +/* max_in_buffer defines the maximum number of bytes that are allowed to be + * in the incoming buffer */ +#define MAX_IN_BUFFER 255 + +#define GREETING(stream) \ + fprintf(stream, "\"IMPLEMENTATION\" \"DBMail timsieved v%s\"\r\n", VERSION); \ + fprintf(stream, "\"SASL\" \"PLAIN\"\r\n"); \ + fprintf(stream, "\"SIEVE\" \"%s\"\r\n", sieve2_listextensions()); \ + fprintf(stream, "OK\r\n") + /* Remember, no trailing semicolon! */ + /* Sadly, some client seem to be hardwired to look to 'timsieved' + * and so that part of the Implementation line is absolutely required. */ + +/* allowed timsieve commands */ +const char *commands [] = +{ + "LOGOUT", "STARTTLS", "CAPABILITY", "LISTSCRIPTS", + "AUTHENTICATE", "DELETESCRIPT", "GETSCRIPT", "SETACTIVE", + "HAVESPACE", "PUTSCRIPT" +}; + +/* \" is added to the standard set of stuff... */ +const char validchars[] = +"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" +"_.!@#$%^&*()-+=~[]{}<>:;\\\"/ "; + +char myhostname[64]; + +int tims_handle_connection(clientinfo_t *ci) +{ + /* + Handles connection and calls + tims command handler + */ + + int done = 1; /* loop state */ + char *buffer = NULL; /* connection buffer */ + int cnt; /* counter */ + time_t timestamp; + + PopSession_t session; /* current connection session */ + + /* setting Session variables */ + session.error_count = 0; + + session.username = NULL; + session.password = NULL; + + session.SessionResult = 0; + + /* reset counters */ + session.totalsize = 0; + session.virtual_totalsize = 0; + session.totalmessages = 0; + session.virtual_totalmessages = 0; + + + /* getting hostname */ + gethostname(myhostname, 64); + myhostname[63] = 0; /* make sure string is terminated */ + + buffer = (char *)my_malloc(INCOMING_BUFFER_SIZE * sizeof(char)); + + if (!buffer) + { + trace(TRACE_MESSAGE, "tims_handle_connection(): Could not allocate buffer"); + return 0; + } + + if (ci->tx) + { + /* This is a macro shared with TIMS_CAPA, per the draft RFC. */ + GREETING(ci->tx); + fflush(ci->tx); + } + else + { + trace(TRACE_MESSAGE, "tims_handle_connection(): TX stream is null!"); + return 0; + } + + while (done > 0) + { + /* set the timeout counter */ + alarm(ci->timeout); + + /* clear the buffer */ + memset(buffer, 0, INCOMING_BUFFER_SIZE); + + for (cnt = 0; cnt < INCOMING_BUFFER_SIZE - 1; cnt++) + { + do + { + clearerr(ci->rx); + fread(&buffer[cnt], 1, 1, ci->rx); + + /* leave, an alarm has occured during fread */ + if (!ci->rx) return 0; + } + while (ferror(ci->rx) && errno == EINTR); + + if (buffer[cnt] == '\n' || feof(ci->rx) || ferror(ci->rx)) + { + if (cnt > 0) + { + /* Ignore single newlines and \r\n pairs */ + if (cnt != 1 || buffer[cnt-1] != '\r') + { + buffer[cnt+1] = '\0'; + break; + } + /* Overwrite those silly extra \r\n's */ + else + { + /* Incremented to 0 at top of loop */ + cnt = -1; + } + } + } + } + + if (feof(ci->rx) || ferror(ci->rx)) + { + /* check client eof */ + done = -1; + } + else + { + /* reset function handle timeout */ + alarm(0); + /* handle tims commands */ + done = tims(ci->tx, ci->rx, buffer, ci->ip, &session); + } + fflush(ci->tx); + } + + /* memory cleanup */ + my_free(buffer); + buffer = NULL; + + /* reset timers */ + alarm (0); + __debug_dumpallocs(); + + return 0; +} + + +int tims_reset(PopSession_t *session) + { + session->state = STRT; + + return 1; + } + + +int tims_error(PopSession_t *session, void *stream, const char *formatstring, ...) +{ + va_list argp; + + if (session->error_count >= MAX_ERRORS) + { + trace(TRACE_MESSAGE, "tims_error(): too many errors (MAX_ERRORS is %d)", MAX_ERRORS); + fprintf((FILE *)stream, "BYE \"Too many errors, closing connection.\"\r\n"); + session->SessionResult = 2; /* possible flood */ + tims_reset(session); + return -3; + } + else + { + va_start(argp, formatstring); + vfprintf((FILE *)stream, formatstring, argp); + va_end(argp); + } + + trace(TRACE_DEBUG, "tims_error(): an invalid command was issued"); + session->error_count++; + return 1; +} + + +int tims(void *stream, void *instream, char *buffer, char *client_ip, PopSession_t *session) +{ + /* returns values: + * 0 to quit + * -1 on failure + * 1 on success */ + char *command, *value; + int cmdtype; + int indx=0; + + /* buffer overflow attempt */ + if (strlen(buffer) > MAX_IN_BUFFER) + { + trace(TRACE_DEBUG, "tims(): buffer overflow attempt"); + return -3; + } + + /* check for command issued */ + while (strchr(validchars, buffer[indx])) + indx++; + + /* end buffer */ + buffer[indx] = '\0'; + + trace(TRACE_DEBUG, "tims(): incoming buffer: [%s]", buffer); + + command = buffer; + + value = strstr(command, " "); /* look for the separator */ + + if (value != NULL) + { + *value = '\0'; /* set a \0 on the command end */ + value++; /* skip space */ + + if (strlen(value) == 0) + { + value = NULL; /* no value specified */ + } + else + { + trace(TRACE_DEBUG, "tims(): command issued: cmd [%s], val [%s]", command, value); + } + } + + for (cmdtype = TIMS_STRT; cmdtype < TIMS_END; cmdtype ++) + if (strcasecmp(command, commands[cmdtype]) == 0) break; + + trace(TRACE_DEBUG, "tims(): command looked up as commandtype %d", cmdtype); + + /* commands that are allowed to have no arguments */ + if ((value == NULL) && !(cmdtype < TIMS_NOARGS) && (cmdtype < TIMS_END)) + { + return tims_error(session, stream, "NO \"This command requires an argument.\"\r\n"); + } + + switch (cmdtype) + { + case TIMS_LOUT : + { + fprintf((FILE *)stream, "OK\r\n"); + tims_reset(session); + return 0; /* return 0 to cause the connection to close */ + } + case TIMS_STLS : + { + /* We don't support TLS, sorry! */ + fprintf((FILE *)stream, "NO\r\n"); + return 1; + } + case TIMS_CAPA : + { + /* This is macro-ized because it is also used in the greeting. */ + GREETING((FILE *)stream); + return 1; + } + case TIMS_AUTH : + { + /* We currently only support plain authentication, + * which means that the command we accept will look + * like this: Authenticate "PLAIN" "base64-password" + * */ + if (strlen(value) > strlen("\"PLAIN\"")) + { + /* Only compare the first part of value */ + if (strncasecmp(value, "\"PLAIN\"", strlen("\"PLAIN\"")) == 0) + { + size_t tmplen=0; + size_t tmppos=0; + char *tmpleft=NULL, **tmp64=NULL; + + /* First see if the base64 SASL is simply quoted */ + if (0 != find_bounded(value+strlen("\"PLAIN\""), '"', '"', &tmpleft, &tmplen, &tmppos)) + { + u64_t authlen; /* Actually, script length must be 32-bit unsigned int. */ + char tmpcharlen[11]; /* A 32-bit unsigned int is ten decimal digits in length. */ + + /* Second, failing that, see if it's an {n+} literal */ + find_bounded(value+strlen("\"PLAIN\""), '{', '+', &tmpleft, &tmplen, &tmppos); + + strncpy(tmpcharlen, tmpleft, (10 < tmplen ? 10 : tmplen)); + tmpcharlen[(10 < tmplen ? 10 : tmplen)] = '\0'; + my_free(tmpleft); + + authlen = strtoull(tmpcharlen, NULL, 10); + if (authlen >= UINT_MAX) + { + fprintf((FILE *)stream, "NO \"Invalid SASL length.\"\r\n"); + tmplen = 0; /* HACK: This prevents the next block from running. */ + } + else + { + if (0 != read_from_stream((FILE *)instream, &tmpleft, authlen)) + { + fprintf((FILE *)stream, "NO \"Error reading SASL.\"\r\n"); + } + else + { + tmplen = authlen; /* HACK: This allows the next block to run. */ + } + } + } + + if (tmplen < 1) + { + /* Definitely an empty password string */ + fprintf((FILE *)stream, "NO \"Password required.\"\r\n"); + } + else + { + size_t i; + u64_t useridnr; + + tmp64 = base64_decode(tmpleft, tmplen); + if (tmp64 == NULL) + { + fprintf((FILE *)stream, "NO \"SASL decode error.\"\r\n"); + } + else + { + for (i = 0; tmp64[i] != NULL; i++) { /* Just count 'em up */ } + if (i < 3) + { + fprintf((FILE *)stream, "NO \"Too few encoded SASL arguments.\"\r\n"); + } + /* The protocol specifies that the base64 encoding + * be made up of three parts: proxy, username, password + * Between them are NULLs, which are conveniently encoded + * by the base64 process... */ + if (auth_validate(tmp64[1], tmp64[2], &useridnr) == 1) + { + fprintf((FILE *)stream, "OK\r\n"); + session->state = AUTH; + session->useridnr = useridnr; + session->username = strdup(tmp64[1]); + session->password = strdup(tmp64[2]); + } + else + { + fprintf((FILE *)stream, "NO \"Username or password incorrect.\"\r\n"); + } + for (i = 0; tmp64[i] != NULL; i++) + { + my_free(tmp64[i]); + } + my_free(tmp64); + } + } /* if... tmplen < 1 */ + } /* if... strncasecmp() == "PLAIN" */ + else + { + trace(TRACE_INFO, "tims(): Input simply was not PLAIN auth"); + fprintf((FILE *)stream, "NO \"Authentication scheme not supported.\"\r\n"); + } + } /* if... strlen() < "PLAIN" */ + else + { + trace(TRACE_INFO, "tims(): Input too short to possibly be PLAIN auth"); + fprintf((FILE *)stream, "NO \"Authentication scheme not supported.\"\r\n"); + } + + return 1; + } + case TIMS_PUTS : + { + if (session->state != AUTH) + { + fprintf((FILE *)stream, "NO \"Please authenticate first.\"\r\n"); + } + else + { + size_t tmplen=0; + size_t tmppos=0; + char *tmpleft=NULL; + + find_bounded(value, '"', '"', &tmpleft, &tmplen, &tmppos); + + if (tmplen < 1) + { + /* Possibly an empty password... */ + fprintf((FILE *)stream, "NO \"Script name required.\"\r\n"); + } + else + { + char scriptname[MAX_SIEVE_SCRIPTNAME+1]; + + strncpy(scriptname, tmpleft, + (MAX_SIEVE_SCRIPTNAME < tmplen ? MAX_SIEVE_SCRIPTNAME : tmplen)); + /* Of course, be sure to NULL terminate, because strncpy() likely won't */ + scriptname[(MAX_SIEVE_SCRIPTNAME < tmplen ? MAX_SIEVE_SCRIPTNAME : tmplen)] = '\0'; + my_free(tmpleft); + + /* Offset from the previous match to make sure not to pull + * the "length" from a script with a malicious name */ + find_bounded(value+tmppos, '{', '+', &tmpleft, &tmplen, &tmppos); + + if (tmplen < 1) + { + /* Possibly an empty password... */ + fprintf((FILE *)stream, "NO \"Length required.\"\r\n"); + } + else + { + u64_t scriptlen; /* Actually, script length must be 32-bit unsigned int. */ + char tmpcharlen[11]; /* A 32-bit unsigned int is ten decimal digits in length. */ + + strncpy(tmpcharlen, tmpleft, (10 < tmplen ? 10 : tmplen)); + tmpcharlen[(10 < tmplen ? 10 : tmplen)] = '\0'; + my_free(tmpleft); + + scriptlen = strtoull(tmpcharlen, NULL, 10); + trace(TRACE_INFO, "%s, %s: Client sending script of length [%llu]", + __FILE__, __FUNCTION__, scriptlen); + if (scriptlen >= UINT_MAX) + { + trace(TRACE_INFO, "%s, %s: Length [%llu] is larger than UINT_MAX [%u]", + __FILE__, __FUNCTION__, scriptlen, UINT_MAX); + fprintf((FILE *)stream, "NO \"Invalid script length.\"\r\n"); + } + else + { + char *f_buf = NULL; + + if (0 != read_from_stream((FILE *)instream, &f_buf, scriptlen)) + { + trace(TRACE_INFO, "%s, %s: Error reading script with read_from_stream()", + __FILE__, __FUNCTION__); + fprintf((FILE *)stream, "NO \"Error reading script.\"\r\n"); + } + else + { + if (0 != db_check_sievescript_quota(session->useridnr, scriptlen)) + { + trace(TRACE_INFO, "%s, %s: Script exceeds user's quota, dumping it", + __FILE__, __FUNCTION__); + fprintf((FILE *)stream, "NO \"Script exceeds available space.\"\r\n"); + } + else + { + char *errmsg = NULL; + + if (0 != sortsieve_script_validate(f_buf, &errmsg)) + { + trace(TRACE_INFO, "%s, %s: Script has syntax errrors: [%s]", + __FILE__, __FUNCTION__, errmsg); + fprintf((FILE *)stream, "NO \"Script error: %s.\"\r\n", errmsg); + } + else + { + /* According to the draft RFC, a script with the same + * name as an existing script should [atomically] replace it. */ + if (0 != db_replace_sievescript(session->useridnr, scriptname, f_buf)) + { + trace(TRACE_INFO, "%s, %s: Error inserting script", + __FILE__, __FUNCTION__); + fprintf((FILE *)stream, "NO \"Error inserting script.\"\r\n"); + } + else + { + trace(TRACE_INFO, "%s, %s: Script successfully received", + __FILE__, __FUNCTION__); + fprintf((FILE *)stream, "OK \"Script successfully received.\"\r\n"); + } + } + my_free(f_buf); + } + } + } + } + } + } + return 1; + } + case TIMS_SETS: + { + if (session->state != AUTH) + { + fprintf((FILE *)stream, "NO \"Please authenticate first.\"\r\n"); + } + else + { + int ret; + size_t tmplen=0; + size_t tmppos=0; + char *tmpleft=NULL; + + find_bounded(value, '"', '"', &tmpleft, &tmplen, &tmppos); + + /* Only activate a new script if one was specified */ + if (tmplen > 0) + { + char scriptname[MAX_SIEVE_SCRIPTNAME+1]; + + strncpy(scriptname, tmpleft, + (MAX_SIEVE_SCRIPTNAME < tmplen ? MAX_SIEVE_SCRIPTNAME : tmplen)); + /* Of course, be sure to NULL terminate, because strncpy() likely won't */ + scriptname[(MAX_SIEVE_SCRIPTNAME < tmplen ? MAX_SIEVE_SCRIPTNAME : tmplen)] = '\0'; + my_free(tmpleft); + + ret = db_activate_sievescript(session->useridnr, scriptname); + if (ret == -3) + { + fprintf((FILE *)stream, "NO \"Script does not exist.\"\r\n"); + return -1; + } + else if (ret != 0) + { + fprintf((FILE *)stream, "NO \"Internal error.\"\r\n"); + return -1; + } + else + { + fprintf((FILE *)stream, "OK \"Script activated.\"\r\n"); + } + } + else + { + char *scriptname=NULL; + ret = db_get_sievescript_active(session->useridnr, &scriptname); + if (scriptname == NULL) + { + fprintf((FILE *)stream, "OK \"No scripts are active at this time.\"\r\n"); + } + else + { + ret = db_deactivate_sievescript(session->useridnr, scriptname); + my_free(scriptname); + if (ret == -3) + { + fprintf((FILE *)stream, "NO \"Active script does not exist.\"\r\n"); + return -1; + } + else if (ret != 0) + { + fprintf((FILE *)stream, "NO \"Internal error.\"\r\n"); + return -1; + } + else + { + fprintf((FILE *)stream, "OK \"All scripts deactivated.\"\r\n"); + } + } + } + } + return 1; + } + case TIMS_GETS: + { + if (session->state != AUTH) + { + fprintf((FILE *)stream, "NO \"Please authenticate first.\"\r\n"); + } + else + { + size_t tmplen=0; + size_t tmppos=0; + char *tmpleft=NULL; + + find_bounded(value, '"', '"', &tmpleft, &tmplen, &tmppos); + + if (tmplen < 1) + { + /* Possibly an empty password... */ + fprintf((FILE *)stream, "NO \"Script name required.\"\r\n"); + } + else + { + int ret = 0; + char *script = NULL; + char scriptname[MAX_SIEVE_SCRIPTNAME+1]; + + strncpy(scriptname, tmpleft, + (MAX_SIEVE_SCRIPTNAME < tmplen ? MAX_SIEVE_SCRIPTNAME : tmplen)); + /* Of course, be sure to NULL terminate, because strncpy() likely won't */ + scriptname[(MAX_SIEVE_SCRIPTNAME < tmplen ? MAX_SIEVE_SCRIPTNAME : tmplen)] = '\0'; + my_free(tmpleft); + + ret = db_get_sievescript_byname(session->useridnr, scriptname, &script); + if (ret == -3) + { + fprintf((FILE *)stream, "NO \"Script not found.\"\r\n"); + } + else if (ret != 0 || script == NULL) + { + fprintf((FILE *)stream, "NO \"Internal error.\"\r\n"); + } + else + { + fprintf((FILE *)stream, "{%u+}\r\n", strlen(script)); + fprintf((FILE *)stream, "%s\r\n", script); + fprintf((FILE *)stream, "OK\r\n"); + } + } + } + return 1; + } + case TIMS_DELS: + { + if (session->state != AUTH) + { + fprintf((FILE *)stream, "NO \"Please authenticate first.\"\r\n"); + } + else + { + size_t tmplen=0; + size_t tmppos=0; + char *tmpleft=NULL; + + find_bounded(value, '"', '"', &tmpleft, &tmplen, &tmppos); + + if (tmplen < 1) + { + /* Possibly an empty password... */ + fprintf((FILE *)stream, "NO \"Script name required.\"\r\n"); + } + else + { + int ret = 0; + char scriptname[MAX_SIEVE_SCRIPTNAME+1]; + + strncpy(scriptname, tmpleft, + (MAX_SIEVE_SCRIPTNAME < tmplen ? MAX_SIEVE_SCRIPTNAME : tmplen)); + /* Of course, be sure to NULL terminate, because strncpy() likely won't */ + scriptname[(MAX_SIEVE_SCRIPTNAME < tmplen ? MAX_SIEVE_SCRIPTNAME : tmplen)] = '\0'; + my_free(tmpleft); + + ret = db_delete_sievescript(session->useridnr, scriptname); + if (ret == -3) + { + fprintf((FILE *)stream, "NO \"Script not found.\"\r\n"); + } + else if (ret != 0) + { + fprintf((FILE *)stream, "NO \"Internal error.\"\r\n"); + } + else + { + fprintf((FILE *)stream, "OK\r\n"); + } + } + } + return 1; + } + case TIMS_SPAC: + { + if (session->state != AUTH) + { + fprintf((FILE *)stream, "NO \"Please authenticate first.\"\r\n"); + } + else + { + fprintf((FILE *)stream, "NO \"Command not implemented.\"\r\n"); + } + return 1; + } + case TIMS_LIST: + { + if (session->state != AUTH) + { + fprintf((FILE *)stream, "NO \"Please authenticate first.\"\r\n"); + } + else + { + struct list scriptlist; + struct element *tmp; + + if(db_get_sievescript_listall(session->useridnr, &scriptlist) < 0) + { + fprintf((FILE *)stream, "NO \"Internal error.\"\r\n"); + } + else + { + if (list_totalnodes(&scriptlist) == 0) + { + /* The command hasn't failed, but there aren't any scripts */ + fprintf((FILE *)stream, "OK \"No scripts found.\"\r\n"); + } + else + { + tmp = list_getstart(&scriptlist); + while (tmp != NULL) + { + struct ssinfo *info = (struct ssinfo *)tmp->data; + fprintf((FILE *)stream, "\"%s\"%s\r\n", + info->name, (info->active == 1 ? " ACTIVE" : "")); + tmp = tmp->nextnode; + } + fprintf((FILE *)stream, "OK\r\n"); + } + if (scriptlist.start) + list_freelist(&scriptlist.start); + } + } + return 1; + } + default : + { + return tims_error(session, stream,"NO \"What are you trying to say here?\"\r\n"); + } + } + return 1; +} + diff --git a/timsieve.h b/timsieve.h new file mode 100644 index 00000000..5bfd4df4 --- /dev/null +++ b/timsieve.h @@ -0,0 +1,79 @@ +/* $Id$ + * (c) 2000-2002 IC&S, The Netherlands + * + * Copied from lmtp.h, in turn from pop3.h, as a starting point - Aaron Stone, 10/8/03 + * This defines some default messages for timsieved */ + +#ifndef _TIMS_H +#define _TIMS_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <time.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#include <sys/wait.h> +#include <signal.h> +#include <time.h> + +#include "misc.h" +#include "list.h" +#include "debug.h" +#include "dbmail.h" +#include "dbmailtypes.h" +#include "clientinfo.h" + +/* processes */ + +#define MAXCHILDREN 5 +#define DEFAULT_CHILDREN 5 + +#define TIMS_DEF_MAXCONNECT 1500 + +/* connection */ + +#define STRT 1 +#define AUTH 2 + +/* allowed tims commands, from tims.c + * The first four take no arguments; + * The next four take one argument; + * The last two take two arguments. +const char *commands [] = +{ + "LOGOUT", "STARTTLS", "CAPABILITY", "LISTSCRIPTS", + "AUTHENTICATE", "DELETESCRIPT", "GETSCRIPT", "SETACTIVE", + "HAVESPACE", "PUTSCRIPT" +}; */ + +#define TIMS_STRT 0 /* lower bound of array - 0 */ +#define TIMS_LOUT 0 +#define TIMS_STLS 1 +#define TIMS_CAPA 2 +#define TIMS_LIST 3 +#define TIMS_NOARGS 4 /* use with if( cmd < TIMS_NOARGS )... */ +#define TIMS_AUTH 4 +#define TIMS_DELS 5 +#define TIMS_GETS 6 +#define TIMS_SETS 7 +#define TIMS_ONEARG 8 /* use with if( cmd < TIMS_ONEARG )... */ +#define TIMS_SPAC 8 +#define TIMS_PUTS 9 +#define TIMS_END 10 /* upper bound of array + 1 */ + +int tims (void *stream, void *instream, char *buffer, char *client_ip, PopSession_t *session); +int tims_handle_connection (clientinfo_t *ci); + +#endif diff --git a/timsieved.c b/timsieved.c new file mode 100644 index 00000000..48313439 --- /dev/null +++ b/timsieved.c @@ -0,0 +1,315 @@ +/* $Id$ +* (c) 2000-2002 IC&S, The Netherlands +* +* timsieved.c +* +* main prg for tims daemon +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> +#include <errno.h> +#include "imap4.h" +#include "server.h" +#include "debug.h" +#include "misc.h" +#include "dbmail.h" +#include "clientinfo.h" +#include "timsieve.h" +#ifdef PROC_TITLES +#include "proctitleutils.h" +#endif + + +#define PNAME "dbmail/timsieved" + +/* server timeout error */ +#define TIMS_TIMEOUT_MSG "221 Connection timeout BYE" + +struct list smtpItems; +struct list timsItems; +struct list sysItems; + +char *configFile = DEFAULT_CONFIG_FILE; + +/* set up database login data */ +extern db_param_t _db_params; + +void SetConfigItems(serverConfig_t *config, struct list *items); +static int SetMainSigHandler(void); +static void Daemonize(void); +void MainSigHandler(int sig, siginfo_t *info, void *data); + +int tims_before_smtp = 0; +int mainRestart = 0; +int mainStop = 0; + +PopSession_t session; +char *myhostname; +char *timeout_setting; + +#ifdef PROC_TITLES +int main(int argc, char *argv[], char **envp) +#else + int main(int argc, char *argv[]) +#endif +{ + serverConfig_t config; + int result, status; + pid_t pid; + + openlog(PNAME, LOG_PID, LOG_MAIL); + + if (argc >= 2 && (argv[1])) + { + if (strcmp (argv[1],"-v") == 0) + { + printf ("\n*** DBMAIL: dbmail-timsieved version $Revision$ %s\n\n",COPYRIGHT); + return 0; + } + else + if (strcmp(argv[1],"-f")==0 && (argv[2])) + configFile = argv[2]; + } + + SetMainSigHandler(); + Daemonize(); + result = 0; + + do + { + mainStop = 0; + mainRestart = 0; + + trace(TRACE_DEBUG, "main(): reading config"); +#ifdef PROC_TITLES + init_set_proc_title(argc, argv, envp, PNAME); + set_proc_title("%s", "Idle"); +#endif + + /* We need smtp config for bounce.c and forward.c */ + ReadConfig("SMTP", configFile, &smtpItems); + ReadConfig("TIMSIEVED", configFile, &timsItems); + ReadConfig("DBMAIL", configFile, &sysItems); + SetConfigItems(&config, &timsItems); + SetTraceLevel(&timsItems); + GetDBParams(&_db_params, &sysItems); + + config.ClientHandler = tims_handle_connection; + config.timeoutMsg = TIMS_TIMEOUT_MSG; + + CreateSocket(&config); + trace(TRACE_DEBUG, "main(): socket created, starting server"); + + switch ( (pid = fork()) ) + { + case -1: + close(config.listenSocket); + trace(TRACE_FATAL, "main(): fork failed [%s]", strerror(errno)); + + case 0: + /* child process */ + drop_privileges(config.serverUser, config.serverGroup); + result = StartServer(&config); + + trace(TRACE_INFO, "main(): server done, exit."); + exit(result); + + default: + /* parent process, wait for child to exit */ + while (waitpid(pid, &status, WNOHANG|WUNTRACED) == 0) + { + if (mainStop) + kill(pid, SIGTERM); + + if (mainRestart) + kill(pid, SIGHUP); + + sleep(2); + } + + if (WIFEXITED(status)) + { + /* child process terminated neatly */ + result = WEXITSTATUS(status); + trace(TRACE_DEBUG, "main(): server has exited, exit status [%d]", result); + } + else + { + /* child stopped or signaled, don't like */ + /* make sure it is dead */ + trace(TRACE_DEBUG, "main(): server has not exited normally. Killing.."); + + kill(pid, SIGKILL); + result = 0; + } + } + + list_freelist(&smtpItems.start); + list_freelist(&timsItems.start); + list_freelist(&sysItems.start); + close(config.listenSocket); + + } while (result == 1 && !mainStop) ; /* 1 means reread-config and restart */ + + trace(TRACE_INFO, "main(): exit"); + return 0; +} + + +void MainSigHandler(int sig, siginfo_t *info, void *data) +{ + trace(TRACE_DEBUG, "MainSigHandler(): got signal [%d]", sig); + + if (sig == SIGHUP) + mainRestart = 1; + else + mainStop = 1; +} + + +void Daemonize() +{ + if (fork()) + exit(0); + setsid(); + + if (fork()) + exit(0); +} + + +int SetMainSigHandler() +{ + struct sigaction act; + + /* init & install signal handlers */ + memset(&act, 0, sizeof(act)); + + act.sa_sigaction = MainSigHandler; + sigemptyset(&act.sa_mask); + act.sa_flags = SA_SIGINFO; + + sigaction(SIGINT, &act, 0); + sigaction(SIGQUIT, &act, 0); + sigaction(SIGTERM, &act, 0); + sigaction(SIGHUP, &act, 0); + + return 0; +} + + +void SetConfigItems(serverConfig_t *config, struct list *items) +{ + field_t val; + + /* read items: NCHILDREN */ + GetConfigValue("NCHILDREN", items, val); + if (strlen(val) == 0) + trace(TRACE_FATAL, "SetConfigItems(): no value for NCHILDREN in config file"); + + if ( (config->nChildren = atoi(val)) <= 0) + trace(TRACE_FATAL, "SetConfigItems(): value for NCHILDREN is invalid: [%d]", config->nChildren); + + trace(TRACE_DEBUG, "SetConfigItems(): server will create [%d] children", config->nChildren); + + + /* read items: MAXCONNECTS */ + GetConfigValue("MAXCONNECTS", items, val); + if (strlen(val) == 0) + trace(TRACE_FATAL, "SetConfigItems(): no value for MAXCONNECTS in config file"); + + if ( (config->childMaxConnect = atoi(val)) <= 0) + trace(TRACE_FATAL, "SetConfigItems(): value for MAXCONNECTS is invalid: [%d]", config->childMaxConnect); + + trace(TRACE_DEBUG, "SetConfigItems(): children will make max. [%d] connections", config->childMaxConnect); + + + /* read items: TIMEOUT */ + GetConfigValue("TIMEOUT", items, val); + if (strlen(val) == 0) + { + trace(TRACE_DEBUG, "SetConfigItems(): no value for TIMEOUT in config file"); + config->timeout = 0; + } + else if ( (config->timeout = atoi(val)) <= 30) + trace(TRACE_FATAL, "SetConfigItems(): value for TIMEOUT is invalid: [%d]", config->timeout); + + trace(TRACE_DEBUG, "SetConfigItems(): timeout [%d] seconds", config->timeout); + + + /* read items: PORT */ + GetConfigValue("PORT", items, val); + if (strlen(val) == 0) + trace(TRACE_FATAL, "SetConfigItems(): no value for PORT in config file"); + + if ( (config->port = atoi(val)) <= 0) + trace(TRACE_FATAL, "SetConfigItems(): value for PORT is invalid: [%d]", config->port); + + trace(TRACE_DEBUG, "SetConfigItems(): binding to PORT [%d]", config->port); + + + /* read items: BINDIP */ + GetConfigValue("BINDIP", items, val); + if (strlen(val) == 0) + trace(TRACE_FATAL, "SetConfigItems(): no value for BINDIP in config file"); + + strncpy(config->ip, val, IPLEN); + config->ip[IPLEN-1] = '\0'; + + trace(TRACE_DEBUG, "SetConfigItems(): binding to IP [%s]", config->ip); + + + /* read items: RESOLVE_IP */ + GetConfigValue("RESOLVE_IP", items, val); + if (strlen(val) == 0) + trace(TRACE_DEBUG, "SetConfigItems(): no value for RESOLVE_IP in config file"); + + config->resolveIP = (strcasecmp(val, "yes") == 0); + + trace(TRACE_DEBUG, "SetConfigItems(): %sresolving client IP", config->resolveIP ? "" : "not "); + + + /* read items: IMAP-BEFORE-SMTP */ + GetConfigValue("TIMS_BEFORE_SMTP", items, val); + if (strlen(val) == 0) + trace(TRACE_DEBUG, "SetConfigItems(): no value for TIMS_BEFORE_SMTP in config file"); + + tims_before_smtp = (strcasecmp(val, "yes") == 0); + + trace(TRACE_DEBUG, "SetConfigItems(): %s TIMS-before-SMTP", + tims_before_smtp ? "Enabling" : "Disabling"); + + + /* read items: EFFECTIVE-USER */ + GetConfigValue("EFFECTIVE_USER", items, val); + if (strlen(val) == 0) + trace(TRACE_FATAL, "SetConfigItems(): no value for EFFECTIVE_USER in config file"); + + strncpy(config->serverUser, val, FIELDSIZE); + config->serverUser[FIELDSIZE-1] = '\0'; + + trace(TRACE_DEBUG, "SetConfigItems(): effective user shall be [%s]", config->serverUser); + + + /* read items: EFFECTIVE-GROUP */ + GetConfigValue("EFFECTIVE_GROUP", items, val); + if (strlen(val) == 0) + trace(TRACE_FATAL, "SetConfigItems(): no value for EFFECTIVE_GROUP in config file"); + + strncpy(config->serverGroup, val, FIELDSIZE); + config->serverGroup[FIELDSIZE-1] = '\0'; + + trace(TRACE_DEBUG, "SetConfigItems(): effective group shall be [%s]", config->serverGroup); + + + +} |