summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--header.c346
-rw-r--r--header.h22
-rw-r--r--lmtp.c717
-rw-r--r--lmtp.h185
-rw-r--r--lmtpd.c314
-rw-r--r--sievecmd.c371
-rw-r--r--sievecmd.h39
-rw-r--r--sort.h33
-rw-r--r--sort/Makefile.am25
-rw-r--r--sort/sort.c337
-rw-r--r--sort/sortsieve.c322
-rw-r--r--sort/sortsieve.h23
-rwxr-xr-xsql/migrate_singe_user.py236
-rw-r--r--timsieve.c736
-rw-r--r--timsieve.h79
-rw-r--r--timsieved.c315
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
diff --git a/lmtp.c b/lmtp.c
new file mode 100644
index 00000000..3cdf428f
--- /dev/null
+++ b/lmtp.c
@@ -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;
+}
+
diff --git a/lmtp.h b/lmtp.h
new file mode 100644
index 00000000..302f104b
--- /dev/null
+++ b/lmtp.h
@@ -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);
+
diff --git a/sort.h b/sort.h
new file mode 100644
index 00000000..103a2e1c
--- /dev/null
+++ b/sort.h
@@ -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);
+
+
+
+}