*** empty log message ***
authorMatthew Mondor <mmondor@pulsar-zone.net>
Wed, 11 Aug 2004 14:35:30 +0000 (14:35 +0000)
committerMatthew Mondor <mmondor@pulsar-zone.net>
Wed, 11 Aug 2004 14:35:30 +0000 (14:35 +0000)
mmsoftware/mmlib/mmsql.c
mmsoftware/mmlib/mmsql.h
mmsoftware/mmmail/ChangeLog
mmsoftware/mmmail/GNUmakefile
mmsoftware/mmmail/scripts/tables.sql
mmsoftware/mmmail/scripts/upgrade-0.0.24.sql
mmsoftware/mmmail/src/mmpop3d/mmpop3d.c
mmsoftware/mmmail/src/mmsmtpd/mmsmtpd.c
mmsoftware/mmmail/src/mmsmtpd/mmsmtpd.h

index 8eb6959..4f9a6fc 100644 (file)
@@ -1,4 +1,4 @@
-/* $Id: mmsql.c,v 1.9 2004/06/04 01:02:33 mmondor Exp $ */
+/* $Id: mmsql.c,v 1.10 2004/08/11 14:35:29 mmondor Exp $ */
 
 /*
  * Copyright (C) 2000-2004, Matthew Mondor
@@ -69,7 +69,7 @@
 
 MMCOPYRIGHT("@(#) Copyright (c) 2000-2004\n\
 \tMatthew Mondor. All rights reserved.\n");
-MMRCSID("$Id: mmsql.c,v 1.9 2004/06/04 01:02:33 mmondor Exp $");
+MMRCSID("$Id: mmsql.c,v 1.10 2004/08/11 14:35:29 mmondor Exp $");
 
 
 
@@ -307,3 +307,20 @@ mmsql_command(const char *query, long len)
 
     return (res);
 }
+
+
+u_int64_t
+mmsql_last_auto_id(void)
+{
+    u_int64_t id;
+
+    if (mmsql_lock)
+       thrfuncs.mutex_lock(mmsql_lock);
+
+    id = mysql_insert_id(mysql);
+
+    if (mmsql_lock)
+       thrfuncs.mutex_unlock(mmsql_lock);
+
+    return id;
+}
index a88ddf6..48e6563 100644 (file)
@@ -1,4 +1,4 @@
-/* $Id: mmsql.h,v 1.6 2004/06/01 19:11:57 mmondor Exp $ */
+/* $Id: mmsql.h,v 1.7 2004/08/11 14:35:29 mmondor Exp $ */
 
 /*
  * Copyright (C) 2000-2004, Matthew Mondor
@@ -79,7 +79,7 @@ extern void           mmsql_ping(void);
 extern MYSQL_RES *     mmsql_query(const char *, long);
 extern MYSQL_RES *     mmsql_free_result(MYSQL_RES *);
 extern bool            mmsql_command(const char *, long);
-
+extern u_int64_t       mmsql_last_auto_id(void);
 
 
 
index d91fb36..410b0a3 100644 (file)
@@ -1,4 +1,4 @@
-$Id: ChangeLog,v 1.40 2004/06/11 02:17:03 mmondor Exp $
+$Id: ChangeLog,v 1.41 2004/08/11 14:35:29 mmondor Exp $
 
 
 
@@ -16,6 +16,30 @@ By     : Matthew Mondor
   - Added STATFAIL_LOGIN and STATFAIL_PASSWORD configuration file options to
     mmpop3d(8) which allow to easily detect failed logins.
   - Added mmpop3d|total|logins mmstat(3) key
+  - mmmail can now store actual message bodies into a maildir similar format
+    (although not compatible with maildir). The old MySQL glob storage format
+    prooved to be quite slow on heavily loaded systems (MySQL being good at
+    small fields but very slow with large glob fields). If MMMAIL_MYSQL is
+    defined at compilation time, old glob MySQL storage will be used in the
+    mail table. On the other hand, if MMMAIL_FILE is defined at compilation
+    time, file storage will be used.
+    For file storage, a directory is automatically created per mailbox if
+    needed into the MMMAIL_FILE_DIR configuration file specified directory.
+    Within each mailbox directory, each message will be stored using a unique
+    filename. MySQL is still used to store an entry for each message, except
+    the body, and for each entry will hold the unique filename of the message.
+    This means that no scanning of the mail directory is needed. The MySQL
+    thread-friendly locking is still used when adding, accessing or deleting
+    messages, using a per-mailbox lock, so that it is safe to assume that
+    operations will be properly serialized, even over NFS. The administrator
+    should only add and delete message through mmsmtpd and mmpop3d, using the
+    SMTP and POP3 protocols, avoiding to modify the directories and files,
+    with the exception that a directory and all its files can safely be
+    deleted after a mailbox was deleted and all its mail entries destroyed in
+    the MySQL database.
+    There is an upgrade-0.0.24.sql script which was added to modify the
+    existing SQL tables to the newly expected format without loss of data.
+    tables.sql also was updated to reflect the new format.
 * Performance enhancements
   - The configuration file parser was rewritten to be more efficient and
     to hold cleaner code than the previous one which was migrated from
@@ -33,6 +57,8 @@ By     : Matthew Mondor
     connections can be dispatched, rather than having to launch a new thread
     per client. We also use this support in the allocation of client-specific
     nodes which can recycle the objects.
+  - The new file storage method for message bodies is much more efficient
+    when used rather than the old MySQL glob storage method.
 * Bug fixes
   - mmstatd(8) and the mmstat(3) library had a bugfix.
   - The message rate sanity checking host-based cache nodes expireing thread
@@ -53,7 +79,7 @@ By     : Matthew Mondor
   - The mmlimitrate(3) library was written to restrict the amount of code
     duplication among my various software which require rate limiting in
     various situations. The software now uses this API for rate throttling
-    (except the bandwidth shaping which is handled by mmfd(3)).
+    (except the bandwidth shaping which is handled by mmfd(3) already).
 
 
 
index 48a9b34..7bc7221 100644 (file)
@@ -1,4 +1,4 @@
-# $Id: GNUmakefile,v 1.3 2004/06/11 00:24:01 mmondor Exp $
+# $Id: GNUmakefile,v 1.4 2004/08/11 14:35:29 mmondor Exp $
 
 MMLIBS := $(addprefix ../mmlib/,mmfd.o mmhash.o mmlimitrate.o mmsql.o \
 mmlog.o mmpool.o mmreadcfg.o mmserver.o mm_pth_pool.o mmstat.o mmstr.o \
@@ -12,6 +12,8 @@ MYSQLLIBDIR := $(shell mysql_config --libs | sed s/\'//g)
 OBJS := src/mmsmtpd/mmsmtpd.o src/mmpop3d/mmpop3d.o
 
 CFLAGS += -Wall
+#CFLAGS += -DMMMAIL_MYSQL
+CFLAGS += -DMMMAIL_FILE
 
 
 all: src/mmsmtpd/mmsmtpd src/mmpop3d/mmpop3d
index 30d0254..d25e1ab 100644 (file)
@@ -1,4 +1,4 @@
-# $Id: tables.sql,v 1.7 2004/08/08 11:14:51 mmondor Exp $
+# $Id: tables.sql,v 1.8 2004/08/11 14:35:29 mmondor Exp $
 #
 # MySQL dump 8.13
 #
@@ -42,7 +42,7 @@ CREATE TABLE box (
 CREATE TABLE mail (
   mail_id bigint(20) NOT NULL auto_increment,
   mail_box varchar(64) NOT NULL default '',
-  mail_file varchar(30),
+  mail_file varchar(255),
   mail_created datetime NOT NULL default '0000-00-00 00:00:00',
   mail_size int(11) NOT NULL default '0',
   mail_data longtext,
index f8a3961..627294d 100644 (file)
@@ -1,4 +1,4 @@
-# $Id: upgrade-0.0.24.sql,v 1.1 2004/08/08 11:14:51 mmondor Exp $
+# $Id: upgrade-0.0.24.sql,v 1.2 2004/08/11 14:35:29 mmondor Exp $
 #
 # You should execute this script if you are upgrading mmmail from 0.0.21 or
 # later to 0.0.24 or later. This adds the new mail_file field for those using
@@ -6,4 +6,4 @@
 #
 
 ALTER TABLE mail MODIFY COLUMN mail_data longtext;
-ALTER TABLE mail ADD COLUMN mail_file varchar(30);
+ALTER TABLE mail ADD COLUMN mail_file varchar(255);
index b4fb803..dab8316 100644 (file)
@@ -1,4 +1,4 @@
-/* $Id: mmpop3d.c,v 1.34 2004/06/11 00:36:03 mmondor Exp $ */
+/* $Id: mmpop3d.c,v 1.35 2004/08/11 14:35:29 mmondor Exp $ */
 
 /*
  * Copyright (C) 2001-2004, Matthew Mondor
@@ -79,7 +79,7 @@
 
 MMCOPYRIGHT("@(#) Copyright (c) 2001-2004\n\
 \tMatthew Mondor. All rights reserved.\n");
-MMRCSID("$Id: mmpop3d.c,v 1.34 2004/06/11 00:36:03 mmondor Exp $");
+MMRCSID("$Id: mmpop3d.c,v 1.35 2004/08/11 14:35:29 mmondor Exp $");
 
 
 
@@ -470,19 +470,19 @@ auth_user(clientenv *clenv)
     fdbuf *fdb = clenv->fdb;
     char *cmdline = clenv->buffer, *args[3], addr[64], *tmp;
 
-    if (clenv->mailbox)
+    if (clenv->mailbox != NULL)
        reply(fdb, FALSE, "Username already specified");
     else {
        if ((mm_straspl(args, cmdline, 2)) == 2) {
            /* Convert first . to @ in username */
-           for (tmp = args[1]; *tmp; tmp++) {
+           for (tmp = args[1]; *tmp != '\0'; tmp++) {
                if (*tmp == '.') {
                    *tmp = '@';
                    break;
                }
            }
            if (valid_address(addr, args[1])) {
-               if (!(clenv->mailbox = mmstrdup(addr)))
+               if ((clenv->mailbox = mmstrdup(addr)) == NULL)
                    nextstate = STATE_ERROR;
                reply(fdb, TRUE, "Username accepted");
            } else
@@ -505,31 +505,31 @@ auth_pass(clientenv *clenv)
     MYSQL_ROW *row;
     unsigned long *lengths;
 
-    if (clenv->mailbox) {
+    if (clenv->mailbox != NULL) {
        if ((mm_straspl(args, cmdline, 2)) == 2) {
            /* Check if user and password are ok,
             * switch state if so, or drop client if not.
             */
-           snprintf(line, 1000,
+           snprintf(line, 1023,
                    "SELECT user_id,user_passwd FROM box LEFT JOIN user "
                    "ON box_user=user_id WHERE box_address='%s' AND "
                    "user_active=1",
                    clenv->mailbox);
            *id = 0;
            *pwhash = 0;
-           if ((mysqlres = mmsql_query(line, mm_strlen(line)))) {
+           if ((mysqlres = mmsql_query(line, mm_strlen(line))) != NULL) {
                if ((mysql_num_rows(mysqlres)) == 1) {
                    if ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres))
                            != NULL) {
                        if ((mysql_num_fields(mysqlres)) == 2) {
                            lengths = mysql_fetch_lengths(mysqlres);
-                           if (row[0]) {
+                           if (row[0] != NULL) {
                                mm_memcpy(id, row[0], lengths[0]);
-                               id[lengths[0]] = 0;
+                               id[lengths[0]] = '\0';
                            }
-                           if (row[1]) {
+                           if (row[1] != NULL) {
                                mm_memcpy(pwhash, row[1], lengths[1]);
-                               pwhash[lengths[1]] = 0;
+                               pwhash[lengths[1]] = '\0';
                            }
                        }
                    }
@@ -553,12 +553,12 @@ auth_pass(clientenv *clenv)
                /* Authentication successful, update box and user tables
                 * and build messages index
                 */
-               snprintf(line, 1000,
+               snprintf(line, 1023,
                        "UPDATE box SET box_out=NOW() WHERE box_address='%s'",
                        clenv->mailbox);
                if (!mmsql_command(line, mm_strlen(line)))
                    DEBUG_PRINTF("auth_pass", "mmsql_command(%s)", line);
-               snprintf(line, 1000,
+               snprintf(line, 1023,
                        "UPDATE user SET user_activity=NOW(),"
                        "user_logins=user_logins+1 WHERE user_id=%s", id);
                if (!mmsql_command(line, mm_strlen(line)))
@@ -645,8 +645,8 @@ main_list(clientenv *clenv)
        /* Single message specified */
        i = atol(args[1]);
        i--;                    /* Array starts at 0, not 1 */
-       if (i < 0 || i > clenv->messages - 1 || !clenv->index
-               || clenv->index[i].deleted) {
+       if (i < 0 || i > clenv->messages - 1 || clenv->index == NULL ||
+               clenv->index[i].deleted) {
            reply(fdb, FALSE, "Out of range");
            REGISTER_ERROR(clenv);
        } else
@@ -655,7 +655,7 @@ main_list(clientenv *clenv)
        /* All messages */
        reply(fdb, TRUE, "%ld message(s) (%ld octets)", clenv->newmessages,
                clenv->newsize);
-       if (clenv->index) {
+       if (clenv->index != NULL) {
            for (i = 0; i < clenv->messages; i++) {
                if (!clenv->index[i].deleted) {
                    if (!fdbprintf(fdb, "%ld %ld\r\n", i + 1,
@@ -687,8 +687,8 @@ main_retr(clientenv *clenv)
     if ((mm_straspl(args, clenv->buffer, 2)) == 2) {
        i = atol(args[1]);
        i--;                    /* Array starts at 0, not 1 */
-       if (i < 0 || !(i < clenv->messages) || !clenv->index
-               || clenv->index[i].deleted) {
+       if (i < 0 || !(i < clenv->messages) || clenv->index == NULL ||
+               clenv->index[i].deleted) {
            reply(fdb, FALSE, "Out of range");
            REGISTER_ERROR(clenv);
        } else {
@@ -720,7 +720,7 @@ main_dele(clientenv *clenv)
     char *args[3];
 
     if ((mm_straspl(args, clenv->buffer, 2)) == 2) {
-       if (clenv->index) {
+       if (clenv->index != NULL) {
            i = atol(args[1]);
            i--;                /* Array starts at 0, not 1 */
            if (i < 0 || !(i < clenv->messages)) {
@@ -772,8 +772,8 @@ main_top(clientenv *clenv)
        i1 = atol(args[1]);
        i2 = atol(args[2]);
        i1--;                   /* Array starts at 0, not 1 */
-       if ((i1 < 0) || !(i1 < clenv->messages) || !clenv->index
-               || clenv->index[i1].deleted) {
+       if ((i1 < 0) || !(i1 < clenv->messages) || clenv->index == NULL ||
+               clenv->index[i1].deleted) {
            reply(fdb, FALSE, "Out of range");
            REGISTER_ERROR(clenv);
        } else {
@@ -809,9 +809,9 @@ main_page(clientenv *clenv)
        i2 = atol(args[2]);
        i3 = atol(args[3]);
        i1--;                   /* Array starts at 0, not 1 */
-       if (i1 < 0 || i2 < 1 || i3 < 1 || i3 > 60
-               || !(i1 < clenv->messages)
-               || !clenv->index || clenv->index[i1].deleted) {
+       if (i1 < 0 || i2 < 1 || i3 < 1 || i3 > 60 ||
+               !(i1 < clenv->messages) || clenv->index == NULL ||
+               clenv->index[i1].deleted) {
            reply(fdb, FALSE, "Out of range");
            REGISTER_ERROR(clenv);
        } else {
@@ -851,7 +851,8 @@ reply(fdbuf *fdb, bool result, const char *fmt, ...)
     va_list arg_ptr;
     bool ret = FALSE;
 
-    if (!result) res = err;
+    if (!result)
+       res = err;
 
     *buf = 0;
     va_start(arg_ptr, fmt);
@@ -877,7 +878,7 @@ alloc_clientenv(void)
     clenv = (clientenv *)pool_alloc(&clenv_pool, TRUE);
     pth_mutex_release(&clenv_lock);
 
-    if (clenv) {
+    if (clenv != NULL) {
        mmstat_init(&clenv->pstat, TRUE, FALSE);
        mmstat_init(&clenv->vstat, TRUE, TRUE);
     }
@@ -895,9 +896,9 @@ init_clientenv(clientenv *clenv)
     msgnode *mnode;
     long i, t;
 
-    if (clenv) {
+    if (clenv != NULL) {
 
-       if ((mnode = clenv->index)) {
+       if ((mnode = clenv->index) != NULL) {
            /* Init message index flags to unread/undeleted */
            for (i = 0, t = clenv->messages; i < t; i++) {
                mnode[i].deleted = FALSE;
@@ -921,8 +922,10 @@ init_clientenv(clientenv *clenv)
 static clientenv *
 free_clientenv(clientenv *clenv)
 {
-    if (clenv->mailbox) mmstrfree(clenv->mailbox);
-    if (clenv->index) free(clenv->index);
+    if (clenv->mailbox != NULL)
+       mmstrfree(clenv->mailbox);
+    if (clenv->index != NULL)
+       free(clenv->index);
 
     pth_mutex_acquire(&clenv_lock, FALSE, NULL);
     pool_free((pnode_t *)clenv);
@@ -1000,13 +1003,14 @@ valid_address(char *to, char *addr)
     mm_strlower(addr);
 
     /* First locate required @ */
-    for (ptr = addr; *ptr && *ptr != '@'; ptr++) ;
-    if (!*ptr)
+    for (ptr = addr; *ptr != '\0' && *ptr != '@'; ptr++) ;
+    if (*ptr == '\0')
        return (FALSE);
 
     /* Then scan to the left */
     for (ptr2 = (ptr - 1); ptr2 >= addr && valid_char(*ptr2); ptr2--) ;
-    if (ptr2 == (ptr - 1)) return (FALSE);
+    if (ptr2 == (ptr - 1))
+       return (FALSE);
     ptr2++;
 
     /* Now validate hostname part */
@@ -1028,28 +1032,32 @@ valid_host(char *host)
     mm_strlower(host);
 
     /* First make sure all characters are valid */
-    for (ptr = host; *ptr; ptr++) {
+    for (ptr = host; *ptr != '\0'; ptr++) {
        if (!valid_char(*ptr)) {
-           *ptr = 0;
+           *ptr = '\0';
            break;
        }
     }
-    if (ptr == host) return (FALSE);
+    if (ptr == host)
+       return (FALSE);
 
     /* Now verify that all parts of the hostname are starting with
      * an alphanumeric char
      */
     ptr = host;
-    while (*ptr) {
-       if (!isalnum(*ptr) || *ptr == ' ') return (FALSE);
+    while (*ptr != '\0') {
+       if (!isalnum(*ptr) || *ptr == ' ')
+           return (FALSE);
        else {
            /* Find next host part */
-           while (*ptr && *ptr != '.') ptr++;
+           while (*ptr != '\0' && *ptr != '.')
+               ptr++;
            if (*ptr == '.') {
                ptr++;
                continue;
            }
-           if (!*ptr) break;
+           if (*ptr == '\0')
+               break;
        }
        ptr++;
     }
@@ -1064,17 +1072,6 @@ valid_host(char *host)
 inline static bool
 valid_char(char c)
 {
-    /*
-    char allow[] = "abcdefghijklmnopqrstuvwxyz1234567890.-_", *ptr = allow;
-
-    while (*ptr) {
-       if (*ptr == c) return (TRUE);
-       ptr++;
-    }
-
-    return (FALSE);
-    */
-
     return (isalnum(c) || c == '.' || c == '-' || c == '_');
 }
 
@@ -1093,31 +1090,32 @@ do_buildindex(clientenv *clenv)
     unsigned long *lengths;
 
     /* Query mysql server */
-    snprintf(line, 1000,
+    snprintf(line, 1023,
            "SELECT mail_id,mail_size FROM mail WHERE mail_box='%s'",
            clenv->mailbox);
-    if ((mysqlres = mmsql_query(line, mm_strlen(line)))) {
+    if ((mysqlres = mmsql_query(line, mm_strlen(line))) != NULL) {
 
-       if ((rows = mysql_num_rows(mysqlres))) {
+       if ((rows = mysql_num_rows(mysqlres)) != 0) {
            /* Allocate array and fill it */
-           if ((mnode = (msgnode *)malloc(rows * sizeof(msgnode)))) {
+           if ((mnode = (msgnode *)malloc(rows * sizeof(msgnode))) != NULL) {
 
                ok = TRUE;
                clenv->size = 0;
                for (i = 0; i < rows; i++) {
-                   if ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres))) {
+                   if ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres))
+                           != NULL) {
                        if ((mysql_num_fields(mysqlres)) == 2) {
                            lengths = mysql_fetch_lengths(mysqlres);
-                           if (row[0]) {
+                           if (row[0] != NULL) {
                                /* Note: this assumes that the field is 20
                                 * bytes
                                 */
                                mm_memcpy(mnode[i].id, row[0], lengths[0]);
-                               mnode[i].id[lengths[0]] = 0;
+                               mnode[i].id[lengths[0]] = '\0';
                                mnode[i].retreived = mnode[i].deleted = FALSE;
-                               if (row[1]) {
+                               if (row[1] != NULL) {
                                    mm_memcpy(line, row[1], lengths[1]);
-                                   line[lengths[1]] = 0;
+                                   line[lengths[1]] = '\0';
                                    size = atol(line);
                                    mnode[i].size = size;
                                    clenv->size += size;
@@ -1176,13 +1174,13 @@ do_retreive(fdbuf *fdb, msgnode *mnode)
 
     snprintf(line, 1000, "SELECT mail_data FROM mail WHERE mail_id=%s",
            mnode->id);
-    if ((mysqlres = mmsql_query(line, mm_strlen(line)))) {
+    if ((mysqlres = mmsql_query(line, mm_strlen(line))) != NULL) {
 
        if ((mysql_num_rows(mysqlres)) == 1) {
-           if ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres))) {
+           if ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres)) != NULL) {
                if ((mysql_num_fields(mysqlres)) == 1) {
                    lengths = mysql_fetch_lengths(mysqlres);
-                   if (row[0]) {
+                   if (row[0] != NULL) {
                        if (reply(fdb, TRUE, "%lu octets", lengths[0])) {
                            fdbflushw(fdb);
                            if (msgwrite(fdb, row[0], lengths[0])) {
@@ -1226,13 +1224,13 @@ do_top(fdbuf *fdb, msgnode *mnode, long lines)
 
     snprintf(line, 1000, "SELECT mail_data FROM mail WHERE mail_id=%s",
            mnode->id);
-    if ((mysqlres = mmsql_query(line, mm_strlen(line)))) {
+    if ((mysqlres = mmsql_query(line, mm_strlen(line))) != NULL) {
 
        if ((mysql_num_rows(mysqlres)) == 1) {
-           if ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres))) {
+           if ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres)) != NULL) {
                if ((fields = mysql_num_fields(mysqlres)) == 1) {
                    lengths = mysql_fetch_lengths(mysqlres);
-                   if (row[0]) {
+                   if (row[0] != NULL) {
                        if (reply(fdb, TRUE,
                                    "%lu octets, also try PAGE <msg> <page> "
                                    "<linesperpage>", lengths[0])) {
@@ -1242,14 +1240,17 @@ do_top(fdbuf *fdb, msgnode *mnode, long lines)
                            tmp = (char *)row[0];
                            /* Find end of headers */
                            i = 0;
-                           while (tmp < to && *tmp && i < 2) {
-                               if (*tmp == '\r') i++;
-                               else if (*tmp != '\n') i = 0;
+                           while (tmp < to && *tmp != '\0' && i < 2) {
+                               if (*tmp == '\r')
+                                   i++;
+                               else if (*tmp != '\n')
+                                   i = 0;
                                tmp++;
                            }
                            /* Find out where ends the specified line */
-                           while (tmp < to && *tmp && lines) {
-                               if (*tmp == '\r') lines--;
+                           while (tmp < to && *tmp != '\0' && lines > 0) {
+                               if (*tmp == '\r')
+                                   lines--;
                                tmp++;
                            }
                            /* Finally print out result */
@@ -1294,36 +1295,41 @@ do_page(fdbuf *fdb, msgnode *mnode, long page, long lines)
     long lfrom, i;
 
     /* Evaluate what the range should be in lines */
-    if (page == 1) lfrom = 0;
-    else lfrom = (page - 1) * lines;
+    if (page == 1)
+       lfrom = 0;
+    else
+       lfrom = (page - 1) * lines;
 
     snprintf(line, 1000, "SELECT mail_data FROM mail WHERE mail_id=%s",
            mnode->id);
-    if ((mysqlres = mmsql_query(line, mm_strlen(line)))) {
+    if ((mysqlres = mmsql_query(line, mm_strlen(line))) != NULL) {
 
        if ((mysql_num_rows(mysqlres)) == 1) {
-           if ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres))) {
+           if ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres)) != NULL) {
                if ((fields = mysql_num_fields(mysqlres)) == 1) {
                    lengths = mysql_fetch_lengths(mysqlres);
-                   if (row[0]) {
+                   if (row[0] != NULL) {
                        if (reply(fdb, TRUE, "%lu octets", lengths[0])) {
                            /* Find out where starts and ends wanted range */
                            i = lfrom;
                            to = (char *)row[0];
                            to += lengths[0];
                            tmp = (char *)row[0];
-                           while (tmp < to && *tmp && i) {
-                               if (*tmp == '\r') i--;
+                           while (tmp < to && *tmp != '\0' && i > 0) {
+                               if (*tmp == '\r')
+                                   i--;
                                tmp++;
                            }
                            if (tmp < to) {
                                from = tmp;
                                i = lines;
-                               while (tmp < to && *tmp && i) {
-                                   if (*tmp == '\r') i--;
+                               while (tmp < to && *tmp != '\0' && i > 0) {
+                                   if (*tmp == '\r')
+                                       i--;
                                    tmp++;
                                }
-                               if (tmp < to) to = tmp;
+                               if (tmp < to)
+                                   to = tmp;
                                if (from != to) {
                                    fdbflushw(fdb);
                                    if (msgwrite(fdb, from, to - from)) {
@@ -1367,7 +1373,7 @@ do_update(clientenv *clenv)
     long i, t, messages, size;
     msgnode *mnode;
 
-    if ((t = clenv->messages)) {
+    if ((t = clenv->messages) > 0) {
 
        /* Deleted messages and bytes */
        messages = 0;
@@ -1392,9 +1398,9 @@ do_update(clientenv *clenv)
                }
            }
        }
-       if (messages && size) {
+       if (messages > 0 && size > 0) {
            /* Update mailbox size */
-           snprintf(line, 1000,
+           snprintf(line, 1023,
                    "UPDATE box SET box_size=box_size-%ld,"
                    "box_msgs=box_msgs-%ld WHERE box_address='%s'",
                    size, messages, clenv->mailbox);
@@ -1481,10 +1487,11 @@ handleclient(unsigned long id, int fd, clientlistnode *clientlnode,
 
 
     if ((fdb = fdbopen(&gfdf, &fdbc, fd, 8192, 8192, CONF.BANDWIDTH_IN * 1024,
-                   CONF.BANDWIDTH_OUT * 1024, timeout, timeout, FALSE))) {
+                   CONF.BANDWIDTH_OUT * 1024, timeout, timeout, FALSE))
+           != NULL) {
 
        /* Allocate our clientenv to share with state functions */
-       if ((clenv = alloc_clientenv())) {
+       if ((clenv = alloc_clientenv()) != NULL) {
 
            /* Query some configuration options from the MySQL server,
             * such as max_rcpts, max_mesg_lines, max_mesg_size, hostname
@@ -1534,7 +1541,8 @@ handleclient(unsigned long id, int fd, clientlistnode *clientlnode,
                        mmsyslog(nod->command->loglevel, LOGLEVEL,
                                "%08X < %s", id, buffer);
 
-                       if ((func = states[state].functions[nod->index])) {
+                       if ((func = states[state].functions[nod->index])
+                               != NULL) {
 
                            /* Valid command, process it in current state */
                            nstate = func(clenv);
@@ -1581,7 +1589,7 @@ handleclient(unsigned long id, int fd, clientlistnode *clientlnode,
            data_out = FDBBYTESW(fdb);
 
            mmstat_transact(&clenv->vstat, TRUE);
-           if (clenv->mailbox)
+           if (clenv->mailbox != NULL)
                mmstat(&clenv->vstat, STAT_UPDATE, -1, "mmpop3d|who|%s",
                        clenv->mailbox);
            mmstat(&clenv->vstat, STAT_UPDATE, -1,
@@ -1589,7 +1597,7 @@ handleclient(unsigned long id, int fd, clientlistnode *clientlnode,
            mmstat_transact(&clenv->vstat, FALSE);
 
            mmstat_transact(&clenv->pstat, TRUE);
-           if (clenv->mailbox) {
+           if (clenv->mailbox != NULL) {
                char *domptr;
 
                /* Record per-mailbox statistics */
@@ -1665,7 +1673,7 @@ _pth_mutex_create(void)
     mnod = (struct mutexnode *)pool_alloc(&mutexes_pool, FALSE);
     pth_mutex_release(&mutexes_lock);
 
-    if (mnod)
+    if (mnod != NULL)
        pth_mutex_init(&mnod->mutex);
 
     return ((void *)mnod);
@@ -1725,7 +1733,10 @@ _pth_eintr(void)
 
 
 /* crypt(3) is not thread-safe, it uses lame static buffer.
- * Let's change this.
+ * Let's change this, using another process with our thread-safe
+ * synchronization provided by mmserver(3)'s async subsystem.
+ * This also delegates DES, MD5 or SHA1 hashing this making the threaded
+ * process more responsive.
  */
 static void
 async_checkpw(struct async_msg *msg)
index 0353d3d..a54d887 100644 (file)
@@ -1,4 +1,4 @@
-/* $Id: mmsmtpd.c,v 1.47 2004/08/08 00:03:05 mmondor Exp $ */
+/* $Id: mmsmtpd.c,v 1.48 2004/08/11 14:35:30 mmondor Exp $ */
 
 /*
  * Copyright (C) 2001-2004, Matthew Mondor
@@ -42,6 +42,7 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <errno.h>
+#include <string.h>    /* strerror(3) */
 
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -78,7 +79,7 @@
 
 MMCOPYRIGHT("@(#) Copyright (c) 2001-2004\n\
 \tMatthew Mondor. All rights reserved.\n");
-MMRCSID("$Id: mmsmtpd.c,v 1.47 2004/08/08 00:03:05 mmondor Exp $");
+MMRCSID("$Id: mmsmtpd.c,v 1.48 2004/08/11 14:35:30 mmondor Exp $");
 
 
 
@@ -216,6 +217,7 @@ main(int argc, char **argv)
        {CAT_STR, CAF_NONE, 1, 31, "DB_USER", CONF.DB_USER},
        {CAT_STR, CAF_NONE, 1, 31, "DB_PASSWORD", CONF.DB_PASSWORD},
        {CAT_STR, CAF_NONE, 1, 31, "DB_DATABASE", CONF.DB_DATABASE},
+       {CAT_STR, CAF_NONE, 1, 255, "MAIL_DIR", CONF.MAIL_DIR},
        {CAT_VAL, CAF_NONE, 1, 32, "ASYNC_PROCESSES", &CONF.ASYNC_PROCESSES},
        {CAT_VAL, CAF_NONE, 1, 9999, "ALLOC_BUFFERS", &CONF.ALLOC_BUFFERS},
        {CAT_VAL, CAF_NONE, 0, 4, "LOG_LEVEL", &CONF.LOG_LEVEL},
@@ -298,6 +300,7 @@ main(int argc, char **argv)
     mm_strcpy(CONF.DB_USER, "mmmail");
     mm_strcpy(CONF.DB_PASSWORD, "mmmailpassword");
     mm_strcpy(CONF.DB_DATABASE, "mmmail");
+    mm_strcpy(CONF.MAIL_DIR, "/var/mmmail-dir");
     CONF.ASYNC_PROCESSES = 3;
     CONF.ALLOC_BUFFERS = 1;
     CONF.LOG_LEVEL = 3;
@@ -371,6 +374,24 @@ main(int argc, char **argv)
     if (!(mm_strcmp(CONF.DB_HOST, "localhost"))) db_host = NULL;
     else db_host = CONF.DB_HOST;
 
+    if (*CONF.MAIL_DIR != '/') {
+       printf("\nMAIL_DIR must be an absolute pathname to a directory\n\n");
+       exit(-1);
+    } else {
+#if defined(MMMAIL_FILE)
+       struct stat st;
+
+       if (stat(CONF.MAIL_DIR, &st) == -1) {
+           printf("\nMAIL_DIR could not be found: '%s'\n\n", CONF.MAIL_DIR);
+           exit(-1);
+       }
+       if (!S_ISDIR(st.st_mode)) {
+           printf("\nMAIL_DIR not a directory: '%s'\n\n", CONF.MAIL_DIR);
+           exit(-1);
+       }
+#endif
+    }
+
     /* Finally init everything */
     openlog(DAEMON_NAME, LOG_PID | LOG_NDELAY, facility);
 
@@ -558,7 +579,7 @@ all_help(clientenv *clenv)
            col = 2;
        }
 
-       if (col)
+       if (col > 0)
            reply(fdb, 214, FALSE, "End of HELP information");
        else {
            reply(fdb, 504, FALSE, "Unknown HELP topic");
@@ -572,11 +593,13 @@ all_help(clientenv *clenv)
        reply(fdb, 214, TRUE, "Available topics:");
        fdbwrite(fdb, "214-", 4);
        col = 1;
-       for (i = 0; (tmp = commands[i].name); i++) {
-           if (commands[i].desc) {
-               if (col == 0) fdbwrite(fdb, "\r\n214-", 6);
+       for (i = 0; (tmp = commands[i].name) != NULL; i++) {
+           if (commands[i].desc != NULL) {
+               if (col == 0)
+                   fdbwrite(fdb, "\r\n214-", 6);
                col++;
-               if (col > 4) col = 0;
+               if (col > 4)
+                   col = 0;
                fdbprintf(fdb, "   %s", tmp);
            }
        }
@@ -597,7 +620,7 @@ all_helo(clientenv *clenv)
     char *args[3], *cmdline = clenv->buffer;
 
     if ((mm_straspl(args, cmdline, 2)) == 2) {
-       if (!clenv->helo) {
+       if (clenv->helo != NULL) {
            if (valid_host(clenv, args[1],
                        CONF.RESOLVE_HELO ? HOST_RES : HOST_NORES, TRUE)) {
                if ((clenv->helo = mmstrdup(args[1])) == NULL)
@@ -628,23 +651,24 @@ all_mail(clientenv *clenv)
     char addr[64];
     bool valid;
 
-    if (!CONF.REQUIRE_HELO || clenv->helo) {
+    if (!CONF.REQUIRE_HELO || clenv->helo != NULL) {
 
-       if (!clenv->from) {
+       if (clenv->from == NULL) {
 
            valid = FALSE;
-           if (!(mm_strncasecmp(" FROM:<>", &clenv->buffer[4], 8))) {
+           if ((mm_strncasecmp(" FROM:<>", &clenv->buffer[4], 8)) == 0) {
                /* Some systems use empty MAIL FROM like this, make sure
                 * that IP address or hostname is allowed to do this.
                 */
                valid = check_nofrom(clenv->c_ipaddr, clenv->c_hostname);
-               if (valid) *addr = 0;
+               if (valid)
+                   *addr = '\0';
            } else
                valid = valid_address(clenv, addr, clenv->buffer,
                        (CONF.RESOLVE_MX_MAIL) ? HOST_RES_MX : HOST_NORES);
 
            if (valid) {
-               if ((clenv->from = (char *)mmstrdup(addr)))
+               if ((clenv->from = (char *)mmstrdup(addr)) != NULL)
                    reply(fdb, 250, FALSE, "Sender ok");
                else
                    nextstate = STATE_ERROR;
@@ -686,7 +710,7 @@ all_rcpt(clientenv *clenv)
     valid = TRUE;
     reason = RCPT_OK;
 
-    if (!clenv->from) {
+    if (clenv->from == NULL) {
        valid = FALSE;
        reason = RCPT_NOFROM;
     }
@@ -851,8 +875,8 @@ all_data(clientenv *clenv)
     int nextstate = STATE_CURRENT;
     fdbuf *fdb = clenv->fdb;
 
-    if (clenv->buffer[4] == 0) {
-       if (clenv->from) {
+    if (clenv->buffer[4] == '\0') {
+       if (clenv->from != NULL) {
            if (DLIST_NODES(&clenv->rcpt) > 0) {
                if (!do_data(clenv))
                    nextstate = STATE_ERROR;
@@ -914,7 +938,7 @@ hash_commands(struct command *cmd, size_t min)
        if (!hashtable_link(&command_table, (hashnode_t *)nod, &nod->hash,
                    sizeof(u_int32_t), TRUE)) {
            DEBUG_PRINTF("hash_commands", "hashtable_link(%s)", cmd->name);
-                                                                                   return FALSE;
+           return FALSE;
        }
     }
 
@@ -963,8 +987,10 @@ reply(fdbuf *fdb, int code, bool cont, const char *fmt, ...)
     vsnprintf(buf, 1023, fmt, arg_ptr);
     va_end(arg_ptr);
 
-    if (cont) err = fdbprintf(fdb, "%d-%s\r\n", code, buf);
-    else err = fdbprintf(fdb, "%d %s\r\n", code, buf);
+    if (cont)
+       err = fdbprintf(fdb, "%d-%s\r\n", code, buf);
+    else
+       err = fdbprintf(fdb, "%d %s\r\n", code, buf);
 
     mmsyslog(3, LOGLEVEL, "> %d (%s)", code, buf);
 
@@ -982,7 +1008,7 @@ alloc_clientenv(void)
     clenv = (clientenv *)pool_alloc(&clenv_pool, TRUE);
     pth_mutex_release(&clenv_lock);
 
-    if (clenv) {
+    if (clenv != NULL) {
        mmstat_init(&clenv->vstat, TRUE, TRUE);
        mmstat_init(&clenv->pstat, TRUE, FALSE);
     }
@@ -997,8 +1023,10 @@ alloc_clientenv(void)
 static bool
 init_clientenv(clientenv *clenv, bool helo)
 {
-    if (helo && clenv->helo) clenv->helo = mmstrfree(clenv->helo);
-    if (clenv->from) clenv->from = mmstrfree(clenv->from);
+    if (helo && clenv->helo != NULL)
+       clenv->helo = mmstrfree(clenv->helo);
+    if (clenv->from != NULL)
+       clenv->from = mmstrfree(clenv->from);
     empty_rcpts(&clenv->rcpt);
 
     return (TRUE);
@@ -1009,8 +1037,10 @@ init_clientenv(clientenv *clenv, bool helo)
 static clientenv *
 free_clientenv(clientenv *clenv)
 {
-    if (clenv->helo) mmstrfree(clenv->helo);
-    if (clenv->from) mmstrfree(clenv->from);
+    if (clenv->helo != NULL)
+       mmstrfree(clenv->helo);
+    if (clenv->from != NULL)
+       mmstrfree(clenv->from);
     empty_rcpts(&clenv->rcpt);
 
     pth_mutex_acquire(&clenv_lock, FALSE, NULL);
@@ -1027,13 +1057,16 @@ free_clientenv(clientenv *clenv)
 static void
 empty_rcpts(list_t *lst)
 {
-    node_t *nod;
+    node_t *nod, *tmp;
 
     pth_mutex_acquire(&rcpt_lock, FALSE, NULL);
-    while ((nod = DLIST_TOP(lst)) != NULL) {
-       DLIST_UNLINK(lst, nod);
+
+    for (nod = DLIST_TOP(lst); nod != NULL; nod = tmp) {
+       tmp = DLIST_NEXT(nod);
        pool_free((pnode_t *)nod);
     }
+    DLIST_INIT(lst);
+
     pth_mutex_release(&rcpt_lock);
 }
 
@@ -1055,7 +1088,7 @@ check_alias(char *addr)
        MYSQL_RES *mysqlres;
 
        mm_strncpy(oaddr, args[0], 63);
-       snprintf(query, 1024, "SELECT alias_pattern,alias_box FROM alias "
+       snprintf(query, 1023, "SELECT alias_pattern,alias_box FROM alias "
                "WHERE alias_domain='%s'", args[1]);
        if ((mysqlres = mmsql_query(query, mm_strlen(query))) != NULL) {
            if ((mysql_num_rows(mysqlres)) > 0) {
@@ -1068,15 +1101,15 @@ check_alias(char *addr)
                while ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres))
                        != NULL) {
                    lengths = mysql_fetch_lengths(mysqlres);
-                   if (row[0] && row[1]) {
+                   if (row[0] != NULL && row[1] != NULL) {
                        mm_memcpy(pat, row[0], lengths[0]);
-                       pat[lengths[0]] = 0;
+                       pat[lengths[0]] = '\0';
                        if ((cur = best_match(oaddr, pat)) != -1) {
                            if (cur > max) {
                                /* Matches better, remember this one */
                                max = cur;
                                mm_memcpy(addr, row[1], lengths[1]);
-                               addr[lengths[1]] = 0;
+                               addr[lengths[1]] = '\0';
                            }
                        }
                    }
@@ -1110,7 +1143,8 @@ check_nofrom(const char *addr, const char *host)
     bool res = FALSE;
     MYSQL_RES *mysqlres;
 
-    if (addr == NULL && host == NULL) return (FALSE);
+    if (addr == NULL && host == NULL)
+       return (FALSE);
 
     if ((mysqlres = mmsql_query("SELECT nofrom_pattern FROM nofrom", 20))
            != NULL) {
@@ -1121,18 +1155,18 @@ check_nofrom(const char *addr, const char *host)
 
            while ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres)) != NULL) {
                lengths = mysql_fetch_lengths(mysqlres);
-               if (row[0]) {
+               if (row[0] != NULL) {
                    char pat[64];
 
                    mm_memcpy(pat, row[0], lengths[0]);
-                   pat[lengths[0]] = 0;
-                   if (addr) {
+                   pat[lengths[0]] = '\0';
+                   if (addr != NULL) {
                        if ((best_match(addr, pat)) != -1) {
                            res = TRUE;
                            break;
                        }
                    }
-                   if (host) {
+                   if (host != NULL) {
                        if ((best_match(host, pat)) != -1) {
                            res = TRUE;
                            break;
@@ -1204,7 +1238,7 @@ local_address(const char *address, long *maxsize, long *size, long *maxmsgs,
     unsigned long *lengths;
 
     /* Query mysql to see if this address exists, and get limits/status */
-    snprintf(line, 1000,
+    snprintf(line, 1023,
             "SELECT box_max_size,box_size,box_max_msgs,box_msgs FROM box "
             "WHERE box_address='%s'", address);
 
@@ -1214,18 +1248,19 @@ local_address(const char *address, long *maxsize, long *size, long *maxmsgs,
            && (row = (MYSQL_ROW *)mysql_fetch_row(mysqlres)) != NULL) {
            if ((fields = mysql_num_fields(mysqlres)) == 4) {
                lengths = mysql_fetch_lengths(mysqlres);
-               if (row[0] && row[1] && row[2] && row[3]) {
+               if (row[0] != NULL && row[1] != NULL && row[2] != NULL &&
+                       row[3] != NULL) {
                    mm_memcpy(line, row[0], lengths[0]);
-                   line[lengths[0]] = 0;
+                   line[lengths[0]] = '\0';
                    *maxsize = atol(line);
                    mm_memcpy(line, row[1], lengths[1]);
-                   line[lengths[1]] = 0;
+                   line[lengths[1]] = '\0';
                    *size = atol(line);
                    mm_memcpy(line, row[2], lengths[2]);
-                   line[lengths[2]] = 0;
+                   line[lengths[2]] = '\0';
                    *maxmsgs = atol(line);
                    mm_memcpy(line, row[3], lengths[3]);
-                   line[lengths[3]] = 0;
+                   line[lengths[3]] = '\0';
                    *msgs = atol(line);
                    res = TRUE;
                } else
@@ -1261,12 +1296,34 @@ rfc_time(char *str)
     gtim = gmtime(&secs);
 
     snprintf(str, 32, "%s, %02d %s %04d %02d:%02d:%02d -0000",
-            days[gtim->tm_wday], gtim->tm_mday, months[gtim->tm_mon],
-            gtim->tm_year + 1900, gtim->tm_hour, gtim->tm_min,
-            gtim->tm_sec);
+           days[gtim->tm_wday], gtim->tm_mday, months[gtim->tm_mon],
+           gtim->tm_year + 1900, gtim->tm_hour, gtim->tm_min,
+           gtim->tm_sec);
 }
 
 
+#if defined(MMMAIL_FILE)
+
+/* Produces time in the format yyyymmddhhmmss. Supplied string must at least
+ * be 15 bytes (16 recommended).
+ */
+static void
+iso_time(char *str)
+{
+    time_t secs;
+    struct tm *gtim;
+
+    secs = time(NULL);
+    gtim = gmtime(&secs);
+
+    snprintf(str, 16, "%04d%02d%02d%02d%02d%02d",
+           gtim->tm_year + 1900, gtim->tm_mon + 1, gtim->tm_mday,
+           gtim->tm_hour, gtim->tm_min, gtim->tm_sec);
+}
+
+#endif
+
+
 /* Returns whether or not supplied address is valid, and if it is return the
  * parsed address in the supplied string. String should be at least 64 bytes.
  */
@@ -1307,7 +1364,8 @@ valid_host(clientenv *clenv, char *host, int res, bool addr)
 {
     register char *ptr;
 
-    if (addr && res != HOST_RES_MX && valid_ipaddress(host)) return (TRUE);
+    if (addr && res != HOST_RES_MX && valid_ipaddress(host))
+       return (TRUE);
 
     mm_strlower(host);
     /* First make sure all characters are valid */
@@ -1401,7 +1459,8 @@ validate_msg_line(char *line, ssize_t *len, int *res, void *udata)
                /* Exceeded maximum allowed number of "Received:" lines */
                *res = CFDBRB_HOPS;
                return (FDBRB_STOP);
-           } else ud->nhops = 0;
+           } else
+               ud->nhops = 0;
        } else {
            ud->nhops++;
            if (ud->nhops > 5)
@@ -1410,7 +1469,7 @@ validate_msg_line(char *line, ssize_t *len, int *res, void *udata)
     }
 
     /* Process .* lines */
-    if (*len) {
+    if (*len > 0) {
        if (*line == '.') {
            /* Only '.' on line, stop reading */
            if (*len == 1)
@@ -1432,11 +1491,9 @@ validate_msg_line(char *line, ssize_t *len, int *res, void *udata)
 static bool
 do_data(clientenv *clenv)
 {
-    char line[512], line2[2048], smtptime[32], *tmp, *query;
     struct fdbrb_buffer *fdbrb;
     int res, err = DATA_INTERNAL;
     bool ok = FALSE;
-    rcptnode *rnode;
     struct validate_udata ud;
 
     reply(clenv->fdb, data_msg[DATA_SUBMIT].code, FALSE,
@@ -1486,115 +1543,30 @@ do_data(clientenv *clenv)
     }
 
     if (ok) {
-       /* Allocate query buffer for mysql_real_query(), should be large
-        * enough to handle the worst of cases where each character would
-        * be escaped to two chars, and must also hold the rest of the
-        * query string. We first process the message data through
-        * mysql_escape_string(), leaving enough room for the query and our
-        * "Received:" line, which will be copied before the message buffer
-        * for each RCPT. The message buffer will start at offset 2048
-        * to make sure that there is enough room to insert the
-        * RCPT-specific data (query+received).
+
+       /* XXX The following could easily simply be provided by separately
+        * compiled mmsmtpd modules, designed to support multple storage
+        * methods, as do_data_store() or such. But, we only support MySQL
+        * and file storage for now... Which suffices for me.
         */
-       if ((query = malloc((fdbrb->current * 2) + 2053))) {
-           size_t len, qlen, tlen, clen;
-           char *domptr;
-
-           /* Prepare message buffer for mysql query */
-           clen = fdbrb->current;      /* Used after freeing buffer as well */
-           tmp = &query[2048];
-           tmp += mysql_escape_string(tmp, fdbrb->array, clen);
-           *tmp++ = '\'';
-           *tmp++ = ')';
-           *tmp++ = '\0';
-           qlen = tmp - &query[2048];
-           rfc_time(smtptime);
-           fdbfreebuf(&fdbrb); /* Free immediately */
-
-           /* For each RCPT, create query and execute it */
-           DLIST_FOREACH(&clenv->rcpt, rnode) {
-               /* Use the common message buffer, but append the query and
-                * message line before it (in it's 2048 bytes free area)
-                */
-               snprintf(line, 511,
-                       "Received: from %s ([%s] HELO=%s)\r\n\tby %s (%s) "
-                       "with SMTP\r\n\tid %08lX-%lu for <%s>;\r\n\t%s\r\n",
-                       clenv->c_hostname ? clenv->c_hostname : "(unresolved)",
-                       clenv->c_ipaddr,
-                       clenv->helo ? clenv->helo : "(unidentified)",
-                       clenv->iface->hostname, DAEMON_VERSION, clenv->id,
-                       clenv->messages, rnode->foraddress, smtptime);
-               tlen = mm_strlen(line) + clen;
-               snprintf(line2, 511,
-                       "INSERT INTO mail (mail_box,mail_created,mail_size,"
-                       "mail_data) VALUES('%s',NOW(),%ld,'",
-                       rnode->address, (long)tlen);
-               tmp = line2 + mm_strlen(line2);
-               tmp += mysql_escape_string(tmp, line, mm_strlen(line));
-               len = tmp - line2;
-               tmp = &query[2048 - len];
-               mm_memcpy(tmp, line2, len);
-
-               /* Query buffer prepared, execute query. This glock is
-                * required for safety between the two queries which have
-                * to be performed within a single transaction. See
-                * mmlib/mmsql.c for implementation details; Currently uses
-                * MySQL GET_LOCK() and RELEASE_LOCK(), which contrary to
-                * table locking will permit to only cause the current thread
-                * to sleep rather than the whole process in this case.
-                */
-               mmsql_glock("mmmail_boxmail");
-               if (!mmsql_command(tmp, qlen + len)) {
-                   DEBUG_PRINTF("do_data", "mmsql_command(%s)", tmp);
-                   ok = FALSE;
-                   break;
-               } else {
-                   snprintf(line, 1000,
-                           "UPDATE box SET box_size=box_size+%ld,"
-                           "box_msgs=box_msgs+1,box_in=NOW() WHERE "
-                           "box_address='%s'",
-                       (long)tlen, rnode->address);
-                   if (!mmsql_command(line, mm_strlen(line))) {
-                       DEBUG_PRINTF("do_data", "mmsql_command(%s)", line);
-                       ok = FALSE;
-                   }
-               }
-               mmsql_gunlock("mmmail_boxmail");
-               if (!ok)
-                   break;
 
-               mmstat_transact(&clenv->pstat, TRUE);
-               /* Record per-box statistics. Note that when aliases are
-                * used, the actual target mailbox is used.
-                */
-               mmstat(&clenv->pstat, STAT_UPDATE, 1,
-                       "mmmail|box|%s|messages-in", rnode->address);
-               mmstat(&clenv->pstat, STAT_UPDATE, tlen,
-                       "mmmail|box|%s|bytes-in", rnode->address);
-               /* And per-domain ones. The address was previously validated
-                * successfully and the '@' character is guarenteed to be
-                * present for mm_strchr().
-                */
-               domptr = mm_strchr(rnode->address, '@');
-               domptr++;
-               mmstat(&clenv->pstat, STAT_UPDATE, 1,
-                       "mmmail|domain|%s|messages-in", domptr);
-               mmstat(&clenv->pstat, STAT_UPDATE, tlen,
-                       "mmmail|domain|%s|bytes-in", domptr);
-               mmstat_transact(&clenv->pstat, FALSE);
-           }
+#if defined(MMMAIL_MYSQL)
+
+       ok = do_data_mysql(clenv, fdbrb);
+
+#elif defined(MMMAIL_FILE)
+
+       ok = do_data_file(clenv, fdbrb);
+
+#else
+#error "One of MMMAIL_MYSQL or MMMAIL_FILE must be #defined!"
+#endif
 
-           free(query);
-       } else {
-           DEBUG_PRINTF("do_data",
-                   "malloc(%d)", (int)(fdbrb->current * 2) + 2053);
-           REGISTER_ERROR(clenv);
-           ok = FALSE;
-       }
     }
 
-    fdbfreebuf(&fdbrb);                /* Internally only frees if not already freed */
-    if (ok) err = DATA_OK;
+    fdbfreebuf(&fdbrb);        /* Internally only frees if not already freed */
+    if (ok)
+       err = DATA_OK;
     reply(clenv->fdb, data_msg[err].code, FALSE, data_msg[err].msg);
 
     /* Reset mail state (and free RCPTs) */
@@ -1603,7 +1575,298 @@ do_data(clientenv *clenv)
     return (ok);
 }
 
+/* Create a Received: line, isolated to prevent code duplication among
+ * different storage methods.
+ */
+inline static void
+do_data_received(char *line, size_t len, clientenv *clenv, rcptnode *rnode,
+       const char *smtptime)
+{
+    snprintf(line, len - 1,
+           "Received: from %s ([%s] HELO=%s)\r\n\tby %s (%s) "
+           "with SMTP\r\n\tid %08lX-%lu for <%s>;\r\n\t%s\r\n",
+           clenv->c_hostname ? clenv->c_hostname : "(unresolved)",
+           clenv->c_ipaddr,
+           clenv->helo ? clenv->helo : "(unidentified)",
+           clenv->iface->hostname, DAEMON_VERSION, clenv->id,
+           clenv->messages, rnode->foraddress, smtptime);
+}
+
+/* Used to update mailbox quotas. Isolated to prevent code duplication among
+ * different storage methods. Note that this must be done under a lock when
+ * necessary for consistency with actual message storage data.
+ */
+inline static bool
+do_data_update(rcptnode *rnode, size_t len)
+{
+    char line[1024];
+    bool ok = TRUE;
+
+    snprintf(line, 1023,
+           "UPDATE box SET box_size=box_size+%ld,"
+           "box_msgs=box_msgs+1,box_in=NOW() WHERE "
+           "box_address='%s'",
+           (long)len, rnode->address);
+    if (!mmsql_command(line, mm_strlen(line))) {
+       DEBUG_PRINTF("do_data", "mmsql_command(%s)", line);
+       ok = FALSE;
+    }
+
+    return ok;
+}
+
+/* Record statistics using mmstat(3) facility, called by do_data to prevent
+ * code duplication among different storage methods.
+ */
+static void
+do_data_stats(clientenv *clenv, rcptnode *rnode, size_t len)
+{
+    char *domptr;
+
+    mmstat_transact(&clenv->pstat, TRUE);
+
+    /* Record per-box statistics. Note that when aliases are used, the actual
+     * target mailbox is used.
+     */
+    mmstat(&clenv->pstat, STAT_UPDATE, 1, "mmmail|box|%s|messages-in",
+           rnode->address);
+    mmstat(&clenv->pstat, STAT_UPDATE, len, "mmmail|box|%s|bytes-in",
+           rnode->address);
+
+    /* And per-domain ones. The address was previously validated successfully
+     * and the '@' character is guarenteed to be present for mm_strchr().
+     */
+    domptr = mm_strchr(rnode->address, '@');
+    domptr++;
+    mmstat(&clenv->pstat, STAT_UPDATE, 1, "mmmail|domain|%s|messages-in",
+           domptr);
+    mmstat(&clenv->pstat, STAT_UPDATE, len, "mmmail|domain|%s|bytes-in",
+           domptr);
+
+    mmstat_transact(&clenv->pstat, FALSE);
+}
+
+
+#if defined(MMMAIL_MYSQL)
+
+static bool
+do_data_mysql(clientenv *clenv, struct fdbrb_buffer *fdbrb)
+{
+    char line[1024], line2[2048], smtptime[32], *tmp, *query;
+    rcptnode *rnode;
+    bool ok = TRUE;
+
+    /* Allocate query buffer for mysql_real_query(), should be large
+     * enough to handle the worst of cases where each character would
+     * be escaped to two chars, and must also hold the rest of the
+     * query string. We first process the message data through
+     * mysql_escape_string(), leaving enough room for the query and our
+     * "Received:" line, which will be copied before the message buffer
+     * for each RCPT. The message buffer will start at offset 2048
+     * to make sure that there is enough room to insert the
+     * RCPT-specific data (query+received).
+     */
+    if ((query = malloc((fdbrb->current * 2) + 2053)) != NULL) {
+       size_t len, qlen, tlen, clen;
+
+       /* Prepare message buffer for mysql query */
+       clen = fdbrb->current;  /* Used after freeing buffer as well */
+       tmp = &query[2048];
+       tmp += mysql_escape_string(tmp, fdbrb->array, clen);
+       *tmp++ = '\'';
+       *tmp++ = ')';
+       *tmp++ = '\0';
+       qlen = tmp - &query[2048];
+       rfc_time(smtptime);
+
+       /* For each RCPT, create query and execute it */
+       DLIST_FOREACH(&clenv->rcpt, rnode) {
+           /* Use the common message buffer, but append the query and
+            * message line before it (in it's 2048 bytes free area)
+            */
+           do_data_received(line, 1024, clenv, rnode, smtptime);
+           tlen = mm_strlen(line) + clen;
+           snprintf(line2, 511,
+                   "INSERT INTO mail (mail_box,mail_created,mail_size,"
+                   "mail_data) VALUES('%s',NOW(),%ld,'",
+                   rnode->address, (long)tlen);
+           tmp = line2 + mm_strlen(line2);
+           tmp += mysql_escape_string(tmp, line, mm_strlen(line));
+           len = tmp - line2;
+           tmp = &query[2048 - len];
+           mm_memcpy(tmp, line2, len);
+
+           /* Query buffer prepared, execute query. This glock is
+            * required for safety between the two queries which have
+            * to be performed within a single transaction. See
+            * mmlib/mmsql.c for implementation details; Currently uses
+            * MySQL GET_LOCK() and RELEASE_LOCK(), which contrary to
+            * table locking will permit to only cause the current thread
+            * to sleep rather than the whole process in this case.
+            */
+           mmsql_glock("mmmail_boxmail");
+           if (!mmsql_command(tmp, qlen + len)) {
+               mmsyslog(0, LOGLEVEL, "mmsql_command(%s)", tmp);
+               ok = FALSE;
+               break;
+           } else {
+               u_int64_t id;
+
+               /* Obtain auto-increment value usd in last command */
+               id = mmsql_last_auto_id();
+
+               if (!(ok = do_data_update(rnode, tlen))) {
+                   /* Delete previous successful entry, since updating quota
+                    * information did not succeed, and it must always be
+                    * accurate according to actual mail data.
+                    */
+                   snprintf(line, 1023,
+                           "DELETE FROM mail WHERE mail_id=%llu", id);
+                   (void) mmsql_command(line, mm_strlen(line));
+               }
+           }
+           mmsql_gunlock("mmmail_boxmail");
+
+           if (!ok)
+               break;
+
+           /* Everything successful, record statistics */
+           do_data_stats(clenv, rnode, tlen);
+       }
+
+       free(query);
+    } else {
+       DEBUG_PRINTF("do_data",
+               "malloc(%d)", (int)(fdbrb->current * 2) + 2053);
+       REGISTER_ERROR(clenv);
+       ok = FALSE;
+    }
+
+    return ok;
+}
+
+#elif defined(MMMAIL_FILE)
+
+static bool
+do_data_file(clientenv *clenv, struct fdbrb_buffer *fdbrb)
+{
+    char smtptime[32], filetime[16], line[1024], path[512];
+    rcptnode *rnode;
+    int i, fd;
+    bool ok;
+    size_t recvlen;
+
+    fd = -1;
+    ok = FALSE;
+
+    rfc_time(smtptime);
 
+    DLIST_FOREACH(&clenv->rcpt, rnode) {
+
+       /* Create Received: line */
+       do_data_received(line, 1024, clenv, rnode, smtptime);
+       recvlen = mm_strlen(line);
+
+       /* Obtain global lock. This ensures that both file and database data
+        * are in sync and between both mmsmtpd and mmpop3d. Moreover, it even
+        * allows proper serialization of operations over NFS.
+        */
+       mmsql_glock("mmmail_boxmail");
+
+       /* Make sure that directory exists, performing an mkdir(2) which will
+        * fail if it already does.
+        */
+       snprintf(path, 511, "%s/%s", CONF.MAIL_DIR, rnode->address);
+       if (mkdir(path, 00750) == -1 && errno != EEXIST) {
+           mmsyslog(0, LOGLEVEL, "mkdir(%s) == %s", path, strerror(errno));
+           continue;
+       }
+
+       /* Generate unique filename to store the message within the mail
+        * directory. We will make 64 retries maximum in an attempt to ensure
+        * creation of a unique filename.
+        */
+       iso_time(filetime);
+       for (i = 0; i < 64; i++) {
+           snprintf(path, 511, "%s/%s/%s.%08X", CONF.MAIL_DIR,
+                   rnode->address, filetime, (u_int32_t)random());
+           if ((fd = open(path, O_CREAT | O_EXCL | O_WRONLY, 00640)) != -1)
+               break;
+       }
+
+       /* Successfully created a new unique file, save message data.
+        * We also verify the return value of close(2), but are not calling
+        * fdatasync(2) or fsync(2) which would considerably impact
+        * performance.
+        */
+       if (fd != -1) {
+           size_t len;
+
+           len = mm_strlen(line);
+           if (write(fd, line, len) == len &&
+                   write(fd, fdbrb->array, fdbrb->current) == fdbrb->current
+                   && close(fd) == 0)
+               ok = TRUE;
+           else {
+               mmsyslog(0, LOGLEVEL, "write()/close()|(%s) == %s",
+                       path, strerror(errno));
+               (void) close(fd);
+               (void) unlink(path);
+           }
+       } else
+           mmsyslog(0, LOGLEVEL, "open(%s) == %s", path, strerror(errno));
+
+       if (ok) {
+           /* File written successfully, now write our corresponding MySQL
+            * entries. Note that we store the absolute fullpath to the
+            * message file into the database entry. Although this is not
+            * necessary, it may prove useful later on.
+            */
+           snprintf(line, 1023,
+                   "INSERT INTO mail (mail_box,mail_created,mail_size,"
+                   "mail_file) VALUES('%s',NOW(),%ld,'%s')",
+                   rnode->address, (long)fdbrb->current + recvlen, path);
+           if (!mmsql_command(line, mm_strlen(line))) {
+               mmsyslog(0, LOGLEVEL, "mmsql_command(%s)", line);
+               ok = FALSE;
+               break;
+           } else {
+               u_int64_t id;
+
+               /* Obtain auto-increment value used in last command */
+               id = mmsql_last_auto_id();
+
+               if (!(ok = do_data_update(rnode, fdbrb->current + recvlen))) {
+                   /* Delete previous successful entry, since updating quota
+                    * information did not succeed, and it must always be
+                    * accurate according to actual mail data.
+                    */
+                   snprintf(line, 1023,
+                           "DELETE FROM mail WHERE mail_id=%llu", id);
+                   (void) mmsql_command(line, mm_strlen(line));
+               }
+           }
+           /* If anything failed, delete stored message file. */
+           if (!ok)
+               (void) unlink(path);
+       }
+
+       /* We can finally safely release the global lock */
+       mmsql_gunlock("mmmail_boxmail");
+
+       if (!ok)
+           break;
+
+       /* Everything successful, update mmstat statistics */
+       do_data_stats(clenv, rnode, fdbrb->current + recvlen);
+    }
+
+    return FALSE;
+}
+
+#else
+#error "One of MMMAIL_MYSQL or MMMAIL_FILE must be #defined!"
+#endif
 
 
 /* This is the main function that is called to serve a client.
@@ -1632,7 +1895,7 @@ handleclient(unsigned long id, int fd, clientlistnode *clientlnode,
     if ((tmp = inet_ntoa(sinaddr->sin_addr))) mm_strncpy(ipaddr, tmp, 19);
     else mm_strncpy(ipaddr, "0.0.0.0", 8);
 
-    if (clientlnode->hostname)
+    if (clientlnode->hostname != NULL)
        /* Log user's address and hostname */
        mmsyslog(1, LOGLEVEL, "%08X Connect: [%s] - (%s)", id, ipaddr,
                clientlnode->hostname);
@@ -1643,10 +1906,11 @@ handleclient(unsigned long id, int fd, clientlistnode *clientlnode,
     time_start = time(NULL);
 
     if ((fdb = fdbopen(&gfdf, &fdbc, fd, 8192, 8192, CONF.BANDWIDTH_IN * 1024,
-                   CONF.BANDWIDTH_OUT * 1024, timeout, timeout, FALSE))) {
+                   CONF.BANDWIDTH_OUT * 1024, timeout, timeout, FALSE))
+           != NULL) {
 
        /* Allocate our clientenv to share with state functions */
-       if ((clenv = alloc_clientenv())) {
+       if ((clenv = alloc_clientenv()) != NULL) {
 
            /* Set some configuration options such as max_rcpts,
             * max_mesg_lines, max_mesg_size, hostname...
@@ -1682,7 +1946,7 @@ handleclient(unsigned long id, int fd, clientlistnode *clientlnode,
                register struct commandnode *nod;
 
                fdbflushw(fdb);
-               if ((len = fdbgets(fdb, buffer, 1000, FALSE)) > -1) {
+               if ((len = fdbgets(fdb, buffer, 1023, FALSE)) > -1) {
 
                    /* If there were too many errors, exit accordingly */
                    if (clenv->errors > CONF.MAX_ERRORS) {
@@ -1701,7 +1965,8 @@ handleclient(unsigned long id, int fd, clientlistnode *clientlnode,
                        mmsyslog(nod->command->loglevel, LOGLEVEL,
                                "%08X < %s", id, buffer);
 
-                       if ((func = states[state].functions[nod->index])) {
+                       if ((func = states[state].functions[nod->index])
+                               != NULL) {
 
                            /* Valid command, process it in current state */
                            nstate = func(clenv);
@@ -1799,7 +2064,7 @@ _pth_mutex_create(void)
     mnod = (struct mutexnode *)pool_alloc(&mutexes_pool, FALSE);
     pth_mutex_release(&mutexes_lock);
 
-    if (mnod)
+    if (mnod != NULL)
        pth_mutex_init(&mnod->mutex);
 
     return ((void *)mnod);
index a9c8b82..5d25634 100644 (file)
@@ -1,4 +1,4 @@
-/* $Id: mmsmtpd.h,v 1.20 2004/05/05 23:59:58 mmondor Exp $ */
+/* $Id: mmsmtpd.h,v 1.21 2004/08/11 14:35:30 mmondor Exp $ */
 
 /*
  * Copyright (C) 2001-2004, Matthew Mondor
@@ -119,7 +119,7 @@ enum data_reason {
 typedef struct config {
     char CHROOT_DIR[256], PID_PATH[256], USER[32], GROUPS[256],
        LOG_FACILITY[32], SERVER_NAMES[1024], LISTEN_IPS[1024], DB_HOST[64],
-       DB_USER[32], DB_PASSWORD[32], DB_DATABASE[32];
+       DB_USER[32], DB_PASSWORD[32], DB_DATABASE[32], MAIL_DIR[256];
     long ALLOC_BUFFERS, LOG_LEVEL, LISTEN_PORT, MAX_ERRORS, MAX_IPS,
        MAX_PER_IP, CONNECTION_RATE, CONNECTION_PERIOD, INPUT_TIMEOUT,
        BANDWIDTH_IN, BANDWIDTH_OUT, GBANDWIDTH_IN, GBANDWIDTH_OUT, MAX_RCPTS,
@@ -259,12 +259,26 @@ static bool check_nofrom(const char *, const char *);
 static int best_match(const char *, const char *);
 static bool local_address(const char *, long *, long *, long *, long *);
 static void rfc_time(char *);
+#if defined(MMMAIL_FILE)
+static void iso_time(char *);
+#endif
 static bool valid_address(clientenv *, char *, char *, int);
 static bool valid_host(clientenv *, char *, int, bool);
 static bool valid_ipaddress(const char *);
 
 static int validate_msg_line(char *, ssize_t *, int *, void *);
 static bool do_data(clientenv *);
+inline static void do_data_received(char *, size_t, clientenv *, rcptnode *,
+       const char *);
+inline static bool do_data_update(rcptnode *, size_t);
+static void do_data_stats(clientenv *, rcptnode *, size_t);
+#if defined(MMMAIL_MYSQL)
+static bool do_data_mysql(clientenv *, struct fdbrb_buffer *);
+#elif defined(MMMAIL_FILE)
+static bool do_data_file(clientenv *, struct fdbrb_buffer *);
+#else
+#error "One of MMMAIL_MYSQL or MMMAIL_FILE must be #defined!"
+#endif
 
 static int handleclient(unsigned long, int, clientlistnode *, struct iface *,
        struct async_clenv *);