Merged pgsql-branch to trunk
authorMatthew Mondor <mmondor@pulsar-zone.net>
Fri, 16 Mar 2007 17:24:11 +0000 (17:24 +0000)
committerMatthew Mondor <mmondor@pulsar-zone.net>
Fri, 16 Mar 2007 17:24:11 +0000 (17:24 +0000)
13 files changed:
mmsoftware/mmlib/mmserver.c
mmsoftware/mmlib/mmserver.h
mmsoftware/mmmail/GNUmakefile
mmsoftware/mmmail/etc/mmpop3d.conf
mmsoftware/mmmail/etc/mmsmtpd.conf
mmsoftware/mmmail/scripts/pgsql-convert.php [new file with mode: 0755]
mmsoftware/mmmail/scripts/pgsql-recycle.sh [new file with mode: 0755]
mmsoftware/mmmail/scripts/pgsql-tables.sql [new file with mode: 0644]
mmsoftware/mmmail/src/mmpop3d/mmpop3d.c
mmsoftware/mmmail/src/mmpop3d/mmpop3d.h
mmsoftware/mmmail/src/mmsmtpd/mmsmtpd.c
mmsoftware/mmmail/src/mmsmtpd/mmsmtpd.h
mmsoftware/mmpasswd/mmpasswd.c

index 659d020..c1e37ed 100644 (file)
@@ -1,4 +1,4 @@
-/* $Id: mmserver.c,v 1.35 2007/03/13 20:28:22 mmondor Exp $ */
+/* $Id: mmserver.c,v 1.36 2007/03/16 17:24:10 mmondor Exp $ */
 
 /*
  * Copyright (C) 2000-2004, Matthew Mondor
@@ -86,7 +86,7 @@
 
 MMCOPYRIGHT("@(#) Copyright (c) 2000-2004\n\
 \tMatthew Mondor. All rights reserved.\n");
-MMRCSID("$Id: mmserver.c,v 1.35 2007/03/13 20:28:22 mmondor Exp $");
+MMRCSID("$Id: mmserver.c,v 1.36 2007/03/16 17:24:10 mmondor Exp $");
 
 
 
@@ -730,12 +730,13 @@ phandleclient(pthread_object_t *obj, void *args)
 /* This consists of a general purpose thread which can serve real
  * asynchroneous functions via another thread, suitable to use for functions
  * which can block the whole process when using non-preemptive threads like
- * the Pth library provides. Pth message ports are used to communicate with
- * this device in a way that processes waiting for results only block
- * the requesting thread. The function internally uses unix datagrams to
- * similarly communicate arguments and obtain back results from a free process
- * in the asynchroneous processes pool. Another advantage of this technique is
- * that on SMP systems the various processors can now be taken advantage of.
+ * the mm_pthread_util library provides. Efficient interthread message ports
+ * are used to communicate with this device in a way that processes waiting for
+ * results only block the requesting thread. The function internally uses unix
+ * datagrams to similarly communicate arguments and obtain back results from a
+ * free process in the asynchroneous processes pool. This system can be used
+ * for parallel processing with a non-preemptive threading library or for
+ * tasks which are best served by another process (i.e. stack issues, etc).
  * The caller should of course expect data rather than pointers to be used for
  * both arguments and return values since pointers are only valid for the
  * current process.
@@ -744,12 +745,12 @@ phandleclient(pthread_object_t *obj, void *args)
  * required, as well as memory copy operations. Moreover, two new
  * filedescriptor are required in the main process for each asynchroneous
  * process in our pool.
- * It should be used where necessary, like calculating MD5 hashes, resolving
+ * It can be used where necessary, like calculating MD5 hashes, resolving
  * hostnames and evaluating directory tree sizes recursively, etc.
  *
  * It would have been possible to use different datagram sizes to transfer
  * arguments and results to/from the other processes, but because of the way
- * the Pth AmigaOS-style messages work, a decision was made so that unions are
+ * AmigaOS-style messages work, a decision was made so that unions are
  * used by async functions and the whole structure is transfered back and
  * forth.
  */
index d730e59..3304001 100644 (file)
@@ -1,4 +1,4 @@
-/* $Id: mmserver.h,v 1.11 2007/03/13 20:28:22 mmondor Exp $ */
+/* $Id: mmserver.h,v 1.12 2007/03/16 17:24:10 mmondor Exp $ */
 
 /*
  * Copyright (C) 2000-2004, Matthew Mondor
@@ -133,18 +133,13 @@ struct ifacendx {
 
 
 
-/* These are for the special asynchroneous functions support for functions Pth
- * cannot provide without blocking the whole process. They will be dispatched
- * to a specialized process through the Pth message passing system.
- */
-
 /* A message which is to pass arguments to the async thread for a function,
  * and to receive the function results. The user structure is usually
  * different for both sides. The msg and func_id fields will be transfered
  * back unchanged. Generally the user provides a fixed size structure using
  * a union to be transfered both ways (results and arguments parts). As the
- * Pth library messages are moved around by simply moving pointers, like on
- * AmigaOs, this is ideal.
+ * mm_pthread_util messages are moved around by simply moving pointers, like
+ * on AmigaOs, this is ideal.
  */
 struct async_msg {
     pnode_t node;
index f1e92e9..66523ee 100644 (file)
@@ -1,22 +1,21 @@
-# $Id: GNUmakefile,v 1.8 2007/03/13 20:28:22 mmondor Exp $
+# $Id: GNUmakefile,v 1.9 2007/03/16 17:24:10 mmondor Exp $
 
-MMLIBS := $(addprefix ../mmlib/,mmfd.o mmhash.o mmlimitrate.o mmsql.o \
-mmlog.o mmpool.o mmreadcfg.o mmserver.o mmstat.o mmstr.o \
-mmstring.o) $(addprefix ../pthread_util/,mm_pthread_msg.o \
-mm_pthread_poll.o mm_pthread_pool.o mm_pthread_sleep.o)
+MMLIBS := $(addprefix ../mmlib/,mmfd.o mmhash.o mmlimitrate.o \
+       mmlog.o mmpool.o mmreadcfg.o mmserver.o mmstat.o mmstr.o \
+       mmstring.o) $(addprefix ../pthread_util/,mm_pthread_msg.o \
+       mm_pthread_poll.o mm_pthread_pool.o mm_pthread_sleep.o)
 
-MYSQL_CFLAGS := $(shell mysql_config --cflags)
-MYSQL_LDFLAGS := $(shell mysql_config --libs)
+PGSQL_CFLAGS := $(shell pg_config --cppflags)
+PGSQL_LDFLAGS := $(shell pg_config --ldflags)
+PGSQL_LDFLAGS += -lpq
 
-CFLAGS += $(MYSQL_CFLAGS) -I. -I../mmlib -I../pthread_util
-LDFLAGS += $(MYSQL_LDFLAGS) -lc -lcrypt -lpthread
+CFLAGS += $(PGSQL_CFLAGS) -I. -I../mmlib -I../pthread_util
+LDFLAGS += $(PGSQL_LDFLAGS) -lc -lcrypt -lpthread
 
 OBJS := src/mmsmtpd/mmsmtpd.o src/mmpop3d/mmpop3d.o src/mmrelayd/mmrelayd.o
 BINS := src/mmsmtpd/mmsmtpd src/mmpop3d/mmpop3d src/mmrelayd/mmrelayd
 
 CFLAGS += -Wall
-#CFLAGS += -DMMMAIL_MYSQL
-CFLAGS += -DMMMAIL_FILE
 #CFLAGS += -DNODETACH -DDEBUG -DPTHREAD_DEBUG
 #LDFLAGS += -lpthread_dbg
 
index 2039090..fc1c7e0 100644 (file)
@@ -1,4 +1,4 @@
-; $Id: mmpop3d.conf,v 1.4 2003/07/12 02:21:25 mmondor Exp $
+; $Id: mmpop3d.conf,v 1.5 2007/03/16 17:24:10 mmondor Exp $
 ;
 ; mmpop3d mmmail component configuration file (/etc/mmpop3d.conf)
 ; and # are considered comments, and can occur at start or end of line.
@@ -17,9 +17,16 @@ ASYNC_PROCESSES      3
 ; files will be required in the new root for instance.
 ;CHROOT_DIR ""
 ;
+; Location of lock file used to determine if daemon already runs
+LOCK_PATH      "/var/run/mmpop3d.lock"
+;
 ; Location of the path where to store our process ID
 PID_PATH       "/var/run/mmpop3d.pid"
 ;
+; Specifies where mail box directories should be created and messages
+; stored in them.
+MAIL_DIR       "/var/mmmail-dir"
+;
 ; User mmpop3d should run as
 USER           "mmmail"
 ; Groups process should be part of
@@ -99,17 +106,8 @@ STATFAIL_LOGIN              FALSE
 STATFAIL_PASSWORD      FALSE
 
 
-; MySQL options
-; -------------
-;
-; Host name of MySQL server we should connect to (localhost for UNIX socket)
-DB_HOST                "localhost"
-;
-; MySQL user which has all access on DB_DATABASE
-DB_USER                "mmmail"
-;
-; MySQL authentication password for DB_USER
-DB_PASSWORD    "mmmailpassword"
+; Database options
+; ----------------
 ;
-; Name of mmmail MySQL database DB_USER owns, typically "mmmail"
-DB_DATABASE    "mmmail"
+; PQconnectdb()-fed string
+DB_INFO                "dbname=mmmail"
index c2227e2..af5f005 100644 (file)
@@ -1,4 +1,4 @@
-; $Id: mmsmtpd.conf,v 1.6 2005/03/05 15:33:33 mmondor Exp $
+; $Id: mmsmtpd.conf,v 1.7 2007/03/16 17:24:10 mmondor Exp $
 ;
 ; mmsmtpd configuration file (/etc/mmsmtpd.conf)
 ; and # are considered comments, and can happen at start or end of line.
@@ -23,10 +23,8 @@ LOCK_PATH    "/var/run/mmsmtpd.lock"
 ; Location of the path where to store our process ID
 PID_PATH       "/var/run/mmsmtpd.pid"
 ;
-; If using MMMAIL_FILE for file storage of message bodies rather than
-; MMMAIL_MYSQL, this specifies where mail box directories should be created
-; and messages stored in them. This should also work fine using NFS.
-;
+; Specifies where mail box directories should be created and messages
+; stored in them.
 MAIL_DIR       "/var/mmmail-dir"
 ;
 ; User mmsmtpd should run as
@@ -159,17 +157,8 @@ FLOOD_MESSAGES     20
 FLOOD_EXPIRES  30
 
 
-; MySQL options
-; -------------
-;
-; Host name of MySQL server we should connect to (localhost for UNIX socket)
-DB_HOST                "localhost"
-;
-; MySQL user which has all access on DB_DATABASE
-DB_USER                "mmmail"
-;
-; MySQL authentication password for DB_USER
-DB_PASSWORD    "mmmailpassword"
+; Database options
+; ----------------
 ;
-; Name of mmmail MySQL database DB_USER owns, typically "mmmail"
-DB_DATABASE    "mmmail"
+; PQconnectdb()-fed string
+DB_INFO                "dbname=mmmail"
diff --git a/mmsoftware/mmmail/scripts/pgsql-convert.php b/mmsoftware/mmmail/scripts/pgsql-convert.php
new file mode 100755 (executable)
index 0000000..aae8aed
--- /dev/null
@@ -0,0 +1,240 @@
+#!/usr/pkg/bin/php
+<?
+/* $Id: pgsql-convert.php,v 1.2 2007/03/16 17:24:10 mmondor Exp $ */
+
+require_once (".password.php");
+
+print("Connecting\n");
+
+if (!($mdb = mysql_connect('localhost', 'mmmail', PASSWORD)))
+       exit(-1);
+if (!mysql_select_db('mmmail', $mdb))
+       exit(-1);
+if (!($pdb = pg_connect("dbname=mmmail")))
+       exit(-1);
+
+function convert_user()
+{
+
+       $res = mysql_query("SELECT * FROM user");
+       while ($f = mysql_fetch_assoc($res)) {
+               foreach ($f as $k => $v2)
+                       $f[$k] = pg_escape_string($f[$k]);
+
+               $q = 'INSERT INTO "user" (id,name,passwd,time_created,' .
+                   "time_activity,logins,active,admin) VALUES(" .
+                   "'{$f['user_id']}'," .
+                   "'{$f['user_name']}'," .
+                   "'{$f['user_passwd']}'," .
+                   "'{$f['user_created']}'," .
+                   "'{$f['user_activity']}'," .
+                   "'{$f['user_logins']}'," .
+                   "'" . (($f['user_active']) ? 't' : 'f') . "'," .
+                   "'" . (($f['user_admin']) ? 't' : 'f') . "')";
+               print("$q\n");
+               if (!($r = pg_query($q)))
+                       exit(-1);
+               pg_free_result($r);
+       }
+}
+
+function convert_box()
+{
+
+       $res = mysql_query("SELECT * FROM box");
+       while ($f = mysql_fetch_assoc($res)) {
+               foreach ($f as $k => $v2)
+                       $f[$k] = pg_escape_string($f[$k]);
+
+               $q = 'INSERT INTO box (address,"user",max_size,size,max_msgs,' .
+                   'msgs,time_created,time_in,time_out,filter,filter_type,' .
+                   'description) VALUES(' .
+                   "'{$f['box_address']}'," .
+                   "'{$f['box_user']}'," .
+                   "'{$f['box_max_size']}'," .
+                   /* "'{$f['box_size']}'," . */ '0,' .
+                   "'{$f['box_max_msgs']}'," .
+                   /* "'{$f['box_msgs']}'," . */ '0,' .
+                   "'{$f['box_created']}'," .
+                   "'{$f['box_in']}'," .
+                   "'{$f['box_out']}'," .
+                   "'" . (($f['box_filter']) ? 't' : 'f') . "'," .
+                   "'" . (($f['box_filter_type'] == 'D') ? 'f' : 't') . "'," .
+                   ($f['box_description'] == '' ? 'NULL' :
+                   "'{$f['box_description']}'") . ")";
+               print("$q\n");
+               if (!($r = pg_query($q)))
+                       exit(-1);
+               pg_free_result($r);
+       }
+}
+
+function convert_alias()
+{
+
+       $res = mysql_query("SELECT * FROM alias");
+       while ($f = mysql_fetch_assoc($res)) {
+               foreach ($f as $k => $v2)
+                       $f[$k] = pg_escape_string($f[$k]);
+
+               $q = "INSERT INTO alias (domain,pattern,box,time_created) " .
+                   "VALUES(" .
+                   "'{$f['alias_domain']}'," .
+                   "'{$f['alias_pattern']}'," .
+                   "'{$f['alias_box']}'," .
+                   "'{$f['alias_created']}')";
+               print("$q\n");
+               if (!($r = pg_query($q)))
+                       exit(-1);
+               pg_free_result($r);
+       }
+}
+
+function convert_nofrom()
+{
+
+       $res = mysql_query("SELECT * FROM nofrom");
+       while ($f = mysql_fetch_assoc($res)) {
+               foreach ($f as $k => $v2)
+                       $f[$k] = pg_escape_string($f[$k]);
+
+               $q = "INSERT INTO nofrom (pattern) VALUES(" .
+                   "'{$f['nofrom_pattern']}')";
+               print("$q\n");
+               if (!($r = pg_query($q)))
+                       exit(-1);
+               pg_free_result($r);
+       }
+}
+
+function convert_relaylocal()
+{
+
+       $res = mysql_query("SELECT * FROM relaylocal");
+       while ($f = mysql_fetch_assoc($res)) {
+               foreach ($f as $k => $v2)
+                       $f[$k] = pg_escape_string($f[$k]);
+
+               $q = "INSERT INTO relaylocal (pattern) VALUES(" .
+                   "'{$f['relaylocal_pattern']}')";
+               print("$q\n");
+               if (!($r = pg_query($q)))
+                       exit(-1);
+               pg_free_result($r);
+       }
+}
+
+function convert_relayfrom()
+{
+
+       $res = mysql_query("SELECT * FROM relayfrom");
+       while ($f = mysql_fetch_assoc($res)) {
+               foreach ($f as $k => $v2)
+                       $f[$k] = pg_escape_string($f[$k]);
+
+               $q = "INSERT INTO relayfrom (pattern) VALUES(" .
+                   "'{$f['relayfrom_pattern']}')";
+               print("$q\n");
+               if (!($r = pg_query($q)))
+                       exit(-1);
+               pg_free_result($r);
+       }
+}
+
+function convert_filter()
+{
+
+       $res = mysql_query("SELECT * FROM filter");
+       while ($f = mysql_fetch_assoc($res)) {
+               foreach ($f as $k => $v2)
+                       $f[$k] = pg_escape_string($f[$k]);
+
+               $q = "INSERT INTO filter (address,pattern,time_created," .
+                   "description) VALUES(" .
+                   "'{$f['filter_address']}'," .
+                   "'{$f['filter_pattern']}'," .
+                   "'{$f['filter_created']}'," .
+                   ($f['filter_description'] == '' ? 'NULL' :
+                   "'{$f['filter_description']}'") . ")";
+               print("$q\n");
+               if (!($r = pg_query($q)))
+                       exit(-1);
+               pg_free_result($r);
+       }
+}
+
+function convert_mail()
+{
+
+       $res = mysql_query("SELECT * FROM mail");
+       while ($f = mysql_fetch_assoc($res)) {
+               foreach ($f as $k => $v2)
+                       $f[$k] = pg_escape_string($f[$k]);
+
+               /* Convert path, only keeping the two last elements */
+               $f['mail_file'] = strtolower($f['mail_box']) . '/' .
+                   basename($f['mail_file']);
+               print("{$f['mail_file']}\n");
+
+               $q = "INSERT INTO mail (id,box,time_created,size,file) VALUES(" .
+                   "'{$f['mail_id']}'," .
+                   "'{$f['mail_box']}'," .
+                   "'{$f['mail_created']}'," .
+                   "'{$f['mail_size']}'," .
+                   "'{$f['mail_file']}')";
+               print("$q\n");
+               if (!($r = pg_query($q)))
+                       exit(-1);
+               pg_free_result($r);
+       }
+}
+
+function convert_relayqueue()
+{
+       /* XXX Flushed the queue instead and started out with an empty one */
+}
+
+function convert_session()
+{
+       /* XXX Flushed sessions */
+}
+
+function sequences()
+{
+
+       $q = "SELECT pg_catalog.SETVAL(pg_catalog.PG_GET_SERIAL_SEQUENCE(" .
+           "'mail', 'id'), (SELECT MAX(id) FROM mail), TRUE)";
+       print("$q\n");
+       if (!$res = pg_exec($q))
+               exit(-1);
+
+       $q = "SELECT pg_catalog.SETVAL(pg_catalog.PG_GET_SERIAL_SEQUENCE(" .
+           "'file_gc_queue', 'id'), (SELECT MAX(id) FROM file_gc_queue), TRUE)";
+       print("$q\n");
+       if (!$res = pg_exec($q))
+               exit(-1);
+
+       $q = "SELECT pg_catalog.SETVAL(pg_catalog.PG_GET_SERIAL_SEQUENCE(" .
+           "'relayqueue', 'id'), (SELECT MAX(id) FROM relayqueue), TRUE)";
+       print("$q\n");
+       if (!$res = pg_exec($q))
+               exit(-1);
+}
+
+convert_user();
+convert_box();
+convert_alias();
+convert_nofrom();
+convert_relaylocal();
+convert_relayfrom();
+convert_filter();
+convert_mail();
+convert_relayqueue();
+convert_session();
+sequences();
+
+print("Disconnecting\n");
+pg_close($pdb);
+mysql_close($mdb);
+
+?>
diff --git a/mmsoftware/mmmail/scripts/pgsql-recycle.sh b/mmsoftware/mmmail/scripts/pgsql-recycle.sh
new file mode 100755 (executable)
index 0000000..9cec264
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/sh
+#
+# $Id: pgsql-recycle.sh,v 1.2 2007/03/16 17:24:10 mmondor Exp $
+
+dropdb mmmail
+createdb -E iso8859-1 mmmail
+psql -d mmmail <pgsql-tables.sql
diff --git a/mmsoftware/mmmail/scripts/pgsql-tables.sql b/mmsoftware/mmmail/scripts/pgsql-tables.sql
new file mode 100644 (file)
index 0000000..56b2fc4
--- /dev/null
@@ -0,0 +1,271 @@
+--- $Id: pgsql-tables.sql,v 1.2 2007/03/16 17:24:10 mmondor Exp $
+
+BEGIN;
+
+
+
+CREATE LANGUAGE plpgsql;
+
+
+
+---
+--- File garbage collection queue.
+--- Mail and box deletion events cause their path to be dumped here.
+--- The application uses a thread to at intervals clear associated files.
+---
+CREATE TABLE "file_gc_queue" (
+       id              bigserial       NOT NULL,
+       type            char(1)         NOT NULL
+                                       CHECK (type = 'd' OR type = 'f'),
+       path            varchar(255)    NOT NULL UNIQUE,
+       PRIMARY KEY (id)
+);
+
+
+
+---
+--- Address/domain patterns we allow mail without a FROM header from
+---
+CREATE TABLE "nofrom" (
+       pattern         varchar(64)     NOT NULL,
+       PRIMARY KEY (pattern)
+);
+
+---
+--- Domains that are considered local (hosted by this server)
+---
+CREATE TABLE "relaylocal" (
+       pattern         varchar(64)     NOT NULL,
+       PRIMARY KEY (pattern)
+);
+
+---
+--- Domains we allow relaying for
+---
+CREATE TABLE "relayfrom" (
+       pattern         varchar(64)     NOT NULL,
+       PRIMARY KEY (pattern)
+);
+
+
+
+---
+--- Describes a user (every box must be tied to a user)
+---
+CREATE TABLE "user" (
+       id              varchar(32)     NOT NULL,
+       name            varchar(64)     NOT NULL,
+       passwd          varchar(34)     NOT NULL,
+       time_created    timestamp       NOT NULL
+                                       DEFAULT CURRENT_TIMESTAMP,
+       time_activity   timestamp       NOT NULL
+                                       DEFAULT CURRENT_TIMESTAMP,
+       logins          bigint          NOT NULL
+                                       DEFAULT 0,
+       active          boolean         NOT NULL
+                                       DEFAULT 't',
+       admin           boolean         NOT NULL
+                                       DEFAULT 'f',
+       PRIMARY KEY (id)
+);
+
+
+
+---
+--- Stores a mailbox associated to a user.
+---
+CREATE TABLE "box" (
+       address         varchar(64)     NOT NULL,
+       "user"          varchar(32)     NOT NULL
+                                       REFERENCES "user" (id)
+                                               ON DELETE CASCADE,
+       max_size        int             NOT NULL,
+       size            int             NOT NULL
+                                       DEFAULT 0,
+       max_msgs        int             NOT NULL,
+       msgs            int             NOT NULL
+                                       DEFAULT 0,
+       time_created    timestamp       NOT NULL
+                                       DEFAULT CURRENT_TIMESTAMP,
+       time_in         timestamp       NOT NULL
+                                       DEFAULT CURRENT_TIMESTAMP,
+       time_out        timestamp       NOT NULL
+                                       DEFAULT CURRENT_TIMESTAMP,
+       time_delete     timestamp       NOT NULL
+                                       DEFAULT CURRENT_TIMESTAMP,
+       filter          boolean         NOT NULL
+                                       DEFAULT 'f',
+       filter_type     boolean         NOT NULL
+                                       DEFAULT 'f',
+       description     varchar(64),
+       PRIMARY KEY (address)
+);
+
+CREATE FUNCTION box_delete() RETURNS trigger AS $box_delete$
+BEGIN
+       INSERT INTO file_gc_queue (type,path) VALUES('d', OLD.address);
+       RETURN NULL;
+END;
+$box_delete$ LANGUAGE plpgsql;
+
+CREATE TRIGGER box_delete
+       AFTER DELETE ON box FOR EACH ROW
+       EXECUTE PROCEDURE box_delete();
+
+
+
+---
+--- box-specific envelope deny/allow filter patterns
+---
+CREATE TABLE "filter" (
+       address         varchar(64)     NOT NULL
+                                       REFERENCES box (address)
+                                               ON DELETE CASCADE,
+       pattern         varchar(128)    NOT NULL,
+       time_created    timestamp       NOT NULL
+                                       DEFAULT CURRENT_TIMESTAMP,
+       description     varchar(64),
+       PRIMARY KEY (address, pattern)
+);
+
+
+
+---
+--- Record to hold a mail post record into a box
+---
+CREATE TABLE "mail" (
+       id              bigserial       NOT NULL,
+       box             varchar(64)     NOT NULL
+                                       REFERENCES box (address)
+                                               ON DELETE CASCADE,
+       time_created    timestamp       NOT NULL
+                                       DEFAULT CURRENT_TIMESTAMP,
+       size            int             NOT NULL,
+       file            varchar(255)    NOT NULL UNIQUE,
+       PRIMARY KEY (id)
+);
+CREATE INDEX mail_box_index ON mail (box);
+
+CREATE FUNCTION mail_add() RETURNS trigger AS $mail_add$
+BEGIN
+       UPDATE box SET
+               size = size + NEW.size,
+               msgs = msgs + 1,
+               time_in = CURRENT_TIMESTAMP
+               WHERE address = NEW.box;
+       RETURN NEW;
+END;
+$mail_add$ LANGUAGE plpgsql;
+
+CREATE TRIGGER mail_add
+       BEFORE INSERT ON mail FOR EACH ROW
+       EXECUTE PROCEDURE mail_add();
+
+CREATE FUNCTION mail_delete() RETURNS trigger AS $mail_delete$
+BEGIN
+       UPDATE box SET
+               size = size - OLD.size,
+               msgs = msgs - 1,
+               time_delete = CURRENT_TIMESTAMP
+               WHERE address = OLD.box;
+       INSERT INTO file_gc_queue (type,path) VALUES('f', OLD.file);
+       RETURN NULL;
+END;
+$mail_delete$ LANGUAGE plpgsql;
+
+CREATE TRIGGER mail_delete
+       AFTER DELETE ON mail FOR EACH ROW
+       EXECUTE PROCEDURE mail_delete();
+
+
+
+---
+--- Alias addresses to local boxes.
+---
+CREATE TABLE "alias" (
+       domain          varchar(64)     NOT NULL,
+       pattern         varchar(64)     NOT NULL,
+       box             varchar(64)     NOT NULL
+                                       REFERENCES box (address)
+                                               ON DELETE CASCADE,
+       time_created    timestamp       NOT NULL
+                                       DEFAULT CURRENT_TIMESTAMP,
+       PRIMARY KEY (domain, pattern, box)
+);
+
+
+
+---
+--- Queue of mail to be relayed out
+---
+CREATE TABLE "relayqueue" (
+       id              bigserial       NOT NULL,
+       "from"          varchar(64)     NOT NULL,
+       ipaddr          inet            NOT NULL,
+       todomain        varchar(64)     NOT NULL,
+       touser          varchar(64)     NOT NULL,
+       size            int             NOT NULL,
+       file            varchar(255)    NOT NULL,
+       time_queued     timestamp       NOT NULL
+                                       DEFAULT CURRENT_TIMESTAMP,
+       time_lasttry    timestamp,
+       time_expires    timestamp       NOT NULL,
+       tries           int             NOT NULL
+                                       DEFAULT 0,
+       lasterror       int             NOT NULL
+                                       DEFAULT 0,
+       PRIMARY KEY (id)
+);
+CREATE INDEX relayqueue_todomain_index ON relayqueue (todomain);
+
+CREATE FUNCTION relayqueue_delete() RETURNS trigger AS $relayqueue_delete$
+BEGIN
+       INSERT INTO file_gc_queue (type,path) VALUES('f', OLD.file);
+       RETURN NULL;
+END;
+$relayqueue_delete$ LANGUAGE plpgsql;
+
+CREATE TRIGGER relayqueue_delete
+       AFTER DELETE ON relayqueue FOR EACH ROW
+       EXECUTE PROCEDURE relayqueue_delete();
+
+
+
+---
+--- Used by the HTTP administration frontend
+---
+CREATE TABLE "session" (
+       id              char(64)        NOT NULL,
+       time_expires    bigint          NOT NULL,
+       data            text            NOT NULL,
+       PRIMARY KEY (id)
+);
+
+
+
+---
+--- Utility function to update the user's activity timestamps
+---
+CREATE FUNCTION user_update(v_user varchar, v_box varchar) RETURNS boolean AS $user_update$
+BEGIN
+       UPDATE box SET time_out = CURRENT_TIMESTAMP WHERE address = v_box;
+       UPDATE "user" SET time_activity = CURRENT_TIMESTAMP WHERE id = v_user;
+       RETURN TRUE;
+END;
+$user_update$ LANGUAGE plpgsql;
+
+
+
+COMMIT;
+
+
+
+---
+--- Give necessary privileges to mmmail user
+---
+GRANT ALL ON TABLE alias, box, file_gc_queue, filter, mail, nofrom,
+       relayfrom, relaylocal, relayqueue, session, "user" TO mmmail;
+GRANT ALL ON file_gc_queue_id_seq, mail_id_seq, relayqueue_id_seq TO mmmail;
+GRANT EXECUTE ON FUNCTION box_delete(), mail_add(), mail_delete(),
+       relayqueue_delete(), user_update(varchar, varchar) TO mmmail;
+--- No views
index fe0e72b..b800bd0 100644 (file)
@@ -1,7 +1,7 @@
-/* $Id: mmpop3d.c,v 1.41 2007/03/13 20:28:22 mmondor Exp $ */
+/* $Id: mmpop3d.c,v 1.42 2007/03/16 17:24:10 mmondor Exp $ */
 
 /*
- * Copyright (C) 2001-2004, Matthew Mondor
+ * Copyright (C) 2001-2007, Matthew Mondor
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -69,7 +69,6 @@
 #include <mmpool.h>
 #include <mmhash.h>
 #include <mmserver.h>
-#include <mmsql.h>
 #include <mmlog.h>
 #include <mmstr.h>
 #include <mmstring.h>
@@ -82,9 +81,9 @@
 
 
 
-MMCOPYRIGHT("@(#) Copyright (c) 2001-2004\n\
+MMCOPYRIGHT("@(#) Copyright (c) 2001-2007\n\
 \tMatthew Mondor. All rights reserved.\n");
-MMRCSID("$Id: mmpop3d.c,v 1.41 2007/03/13 20:28:22 mmondor Exp $");
+MMRCSID("$Id: mmpop3d.c,v 1.42 2007/03/16 17:24:10 mmondor Exp $");
 
 
 
@@ -199,7 +198,6 @@ main(int argc, char **argv)
     char *conf_file = "/usr/local/etc/mmpop3d.conf";
     int ret = -1, ngids;
     long facility;
-    char *db_host;
     bool strlist;
     cres_t cres;
     carg_t *cargp;
@@ -212,10 +210,8 @@ main(int argc, char **argv)
        {CAT_STR, CAF_NONE, 1, 31, "LOG_FACILITY", CONF.LOG_FACILITY},
        {CAT_STR, CAF_NONE, 1, 1023, "LISTEN_IPS", CONF.LISTEN_IPS},
        {CAT_STR, CAF_NONE, 1, 1023, "SERVER_NAMES", CONF.SERVER_NAMES},
-       {CAT_STR, CAF_NONE, 1, 63, "DB_HOST", CONF.DB_HOST},
-       {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, 1023, "DB_INFO", CONF.DB_INFO},
+       {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},
@@ -257,13 +253,6 @@ main(int argc, char **argv)
        {async_checkpw, sizeof(struct async_checkpw_msg)},
        {NULL, 0}
     };
-    struct mmsql_threadsupport mmsqlfuncs = {
-       thread_mutex_create,
-       thread_mutex_destroy, 
-       thread_mutex_lock,
-       thread_mutex_unlock,
-       pthread_sleep
-    };
     mmstat_t vstat;
 
     /* Set defaults */
@@ -275,10 +264,8 @@ main(int argc, char **argv)
     mm_strcpy(CONF.LOG_FACILITY, "LOG_AUTHPRIV");
     mm_strcpy(CONF.LISTEN_IPS, "127.0.0.1");
     mm_strcpy(CONF.SERVER_NAMES, "pop3.localhost");
-    mm_strcpy(CONF.DB_HOST, "localhost");
-    mm_strcpy(CONF.DB_USER, "mmmail");
-    mm_strcpy(CONF.DB_PASSWORD, "mmmailpassword");
-    mm_strcpy(CONF.DB_DATABASE, "mmmail");
+    mm_strcpy(CONF.DB_INFO, "dbname=mmmail");
+    mm_strcpy(CONF.MAIL_DIR, "/var/mmmail-dir");
     CONF.ASYNC_PROCESSES = 3;
     CONF.ALLOC_BUFFERS = 1;
     CONF.LOG_LEVEL = 3;
@@ -342,8 +329,6 @@ main(int argc, char **argv)
        printf("\nOne of following groups unknown: '%s'\n\n", CONF.GROUPS);
        exit(-1);
     }
-    if (!(mm_strcmp(CONF.DB_HOST, "localhost"))) db_host = NULL;
-    else db_host = CONF.DB_HOST;
 
     /* Finally init everything */
     openlog(DAEMON_NAME, LOG_PID | LOG_NDELAY, facility);
@@ -369,12 +354,6 @@ main(int argc, char **argv)
     mmstat(&vstat, STAT_DELETE, 0, "mmpop3d|current|connections");
     mmstat(&vstat, STAT_DELETE, 0, "mmpop3d|who|*");
     mmstat_transact(&vstat, FALSE);
-    if (!(mmsql_open(db_host, CONF.DB_USER, CONF.DB_PASSWORD,
-                   CONF.DB_DATABASE))) {
-       printf("\nCould not connect to MySQLd\n\n");
-       syslog(LOG_NOTICE, "* Could not connect to MySQLd");
-       exit(-1);
-    }
 
     make_daemon(CONF.PID_PATH, CONF.CHROOT_DIR);
 
@@ -386,8 +365,8 @@ main(int argc, char **argv)
 
     /* Allocate necessary pools */
     /* Client nodes */
-    pool_init(&clenv_pool, "clenv_pool", malloc, free, NULL, NULL,
-           sizeof(clientenv),
+    pool_init(&clenv_pool, "clenv_pool", malloc, free, clenv_constructor,
+           clenv_destructor, sizeof(clientenv),
            (65536 * CONF.ALLOC_BUFFERS) / sizeof(clientenv), 0, 0);
     /* Mutexes pool for mmfd */
     pool_init(&mutexes_pool, "mutexes_pool", malloc, free, NULL, NULL,
@@ -401,7 +380,6 @@ main(int argc, char **argv)
        thread_init();
        fdbcinit(&gfdf, &fdbc, CONF.GBANDWIDTH_IN * 1024,
                CONF.GBANDWIDTH_OUT * 1024);
-       mmsql_init(&mmsqlfuncs);
 
        tcp_server("-ERR Server too busy, try again\r\n",
                CONF.SERVER_NAMES, CONF.LISTEN_IPS, uid, gids, ngids,
@@ -411,8 +389,6 @@ main(int argc, char **argv)
 
        mmfreegidarray(gids);
        ret = 0;
-       mmsql_close();
-       mmsql_exit();
     } else {
        printf("\nOut of memory\n\n");
        syslog(LOG_NOTICE, "* Out of memory");
@@ -510,45 +486,31 @@ auth_user(clientenv *clenv)
 static int
 auth_pass(clientenv *clenv)
 {
-    int nextstate = STATE_CURRENT;
-    fdbuf *fdb = clenv->fdb;
-    char *cmdline = clenv->buffer, *args[3], line[1024], pwhash[36], id[33];
-    MYSQL_RES *mysqlres;
-    MYSQL_ROW *row;
-    unsigned long *lengths;
+    int        nextstate = STATE_CURRENT;
+    fdbuf      *fdb = clenv->fdb;
+    char       *cmdline = clenv->buffer, *args[3], pwhash[36], id[33];
+    PGresult   *pgres;
+    const char *params[3];
 
     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, 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))) != 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] != NULL) {
-                               mm_memcpy(id, row[0], lengths[0]);
-                               id[lengths[0]] = '\0';
-                           }
-                           if (row[1] != NULL) {
-                               mm_memcpy(pwhash, row[1], lengths[1]);
-                               pwhash[lengths[1]] = '\0';
-                           }
-                       }
-                   }
+           *pwhash = *id = '\0';
+           params[0] = clenv->mailbox;
+           params[1] = NULL;
+           if ((pgres = PQexecParams(clenv->pgconn,
+                   "SELECT \"user\",passwd FROM box LEFT JOIN \"user\" ON "
+                   "\"user\"=id WHERE address=$1 AND active='t'",
+                   1, NULL, params, NULL, NULL, 0)) != NULL) {
+               if (PQntuples(pgres) == 1) {
+                   mm_strcpy(id, PQgetvalue(pgres, 0, 0));
+                   mm_strcpy(pwhash, PQgetvalue(pgres, 0, 1));
                }
-               mysqlres = mmsql_free_result(mysqlres);
+               PQclear(pgres);
            } else
-               DEBUG_PRINTF("auth_pass", "mmsql_query(%s)", line);
+               syslog(LOG_NOTICE, "auth_pass() - PQexecParams()");
            if (*pwhash == '\0') {
                /* User does not exist */
                mmsyslog(0, LOGLEVEL, "%08X Failed login for %s (unexisting)",
@@ -559,22 +521,21 @@ auth_pass(clientenv *clenv)
                clenv->mailbox = mmstrfree(clenv->mailbox);
                nextstate = STATE_END;
                reply(fdb, FALSE, "Authentication failiure");
-           } else if(checkpw(clenv, args[1], pwhash)) {
+           } else if (checkpw(clenv, args[1], pwhash)) {
                char *domptr;
 
                /* Authentication successful, update box and user tables
                 * and build messages index
                 */
-               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, 1023,
-                       "UPDATE user SET user_activity=NOW(),"
-                       "user_logins=user_logins+1 WHERE user_id='%s'", id);
-               if (!mmsql_command(line, mm_strlen(line)))
-                   DEBUG_PRINTF("auth_pass", "mmsql_command(%s)", line);
+               params[0] = id;
+               params[1] = clenv->mailbox;
+               params[2] = NULL;
+               if ((pgres = PQexecParams(clenv->pgconn,
+                       "SELECT user_update($1,$2)", 2,
+                       NULL, params, NULL, NULL, 0)) != NULL)
+                   PQclear(pgres);
+               else
+                   syslog(LOG_NOTICE, "auth_pass() - PQexecParams(2)");
                clenv->login = TRUE;
                if (!do_buildindex(clenv)) {
                    reply(fdb, FALSE, "Error");
@@ -880,6 +841,38 @@ reply(fdbuf *fdb, bool result, const char *fmt, ...)
 }
 
 
+static bool
+clenv_constructor(pnode_t *pn)
+{
+       clientenv       *clenv = (clientenv *)pn;
+
+       mmstat_init(&clenv->vstat, TRUE, TRUE);
+       mmstat_init(&clenv->pstat, TRUE, FALSE);
+
+       /* XXX Performance enhancement but requires thread safety
+       if ((clenv->pgconn = PQconnectdb(CONF.DB_INFO)) == NULL) {
+           mmsyslog(LOGLEVEL, 0, "PQconnectdb()");
+           return FALSE;
+       }
+       */
+
+       return TRUE;
+}
+
+static void
+clenv_destructor(pnode_t *pn)
+{
+       /* XXX Performance enhancement but requires thread safety
+       clientenv       *clenv = (clientenv *)pn;
+
+       if (clenv->pgconn != NULL) {
+           PQfinish(clenv->pgconn);
+           clenv->pgconn = NULL;
+       }
+       */
+}
+
+
 /* Allocate and prepare a clenv. Returns NULL on error */
 static clientenv *
 alloc_clientenv(void)
@@ -890,9 +883,10 @@ alloc_clientenv(void)
     clenv = (clientenv *)pool_alloc(&clenv_pool, TRUE);
     pthread_mutex_unlock(&clenv_lock);
 
-    if (clenv != NULL) {
-       mmstat_init(&clenv->pstat, TRUE, FALSE);
-       mmstat_init(&clenv->vstat, TRUE, TRUE);
+    /* XXX for now */
+    if ((clenv->pgconn = PQconnectdb(CONF.DB_INFO)) == NULL) {
+       syslog(LOG_NOTICE, "alloc_clientenv() - PQconnectdb()");
+       return free_clientenv(clenv);
     }
 
     return (clenv);
@@ -939,6 +933,10 @@ free_clientenv(clientenv *clenv)
     if (clenv->index != NULL)
        free(clenv->index);
 
+    /* XXX for now */
+    if (clenv->pgconn != NULL)
+               PQfinish(clenv->pgconn);
+
     pthread_mutex_lock(&clenv_lock);
     pool_free((pnode_t *)clenv);
     pthread_mutex_unlock(&clenv_lock);
@@ -1110,150 +1108,50 @@ valid_char(char c)
 static bool
 do_buildindex(clientenv *clenv)
 {
-    char line[1024];
-    bool ok = FALSE;
-    msgnode *mnode;
-    int rows, i, numfields;
-    long size;
-    MYSQL_RES *mysqlres;
-    MYSQL_ROW *row;
-    unsigned long *lengths;
-
-    /* Query mysql server */
-#if defined(MMMAIL_MYSQL)
-    snprintf(line, 1023,
-           "SELECT mail_id,mail_size FROM mail WHERE mail_box='%s'",
-           clenv->mailbox);
-    numfields = 2;
-#elif defined(MMMAIL_FILE)
-    snprintf(line, 1023,
-           "SELECT mail_id,mail_size,mail_file FROM mail WHERE "
-           "mail_box='%s'", clenv->mailbox);
-    numfields = 3;
-#else
-#error "One of MMMAIL_FILE or MMMAIL_MYSQL must be #defined!"
-#endif
-
-    if ((mysqlres = mmsql_query(line, mm_strlen(line))) != NULL) {
-
-       if ((rows = mysql_num_rows(mysqlres)) != 0) {
-           /* Allocate array and fill it */
-           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))
-                           != NULL) {
-                       if ((mysql_num_fields(mysqlres)) == numfields) {
-                           lengths = mysql_fetch_lengths(mysqlres);
-                           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].retreived = mnode[i].deleted = FALSE;
-                               if (row[1] != NULL) {
-                                   mm_memcpy(line, row[1], lengths[1]);
-                                   line[lengths[1]] = '\0';
-                                   size = atol(line);
-                                   mnode[i].size = size;
-                                   clenv->size += size;
-                               } else {
-                                   ok = FALSE;
-                                   break;
-                               }
-#if defined(MMMAIL_FILE)
-                               if (row[2] != NULL) {
-                                   mm_memcpy(mnode[i].file, row[2],
-                                           lengths[2]);
-                                   mnode[i].file[lengths[2]] = '\0';
-                               } else {
-                                   ok = FALSE;
-                                   break;
-                               }
-#endif
-                           } else {
-                               ok = FALSE;
-                               break;
-                           }
-                       } else {
-                           DEBUG_PRINTF("do_buildindex",
-                                   "mysql_num_fields() != %d", numfields);
-                           ok = FALSE;
-                           break;
-                       }
-                   } else {
-                       DEBUG_PRINTF("do_buildindex", "mysql_fetch_rows()");
-                       ok = FALSE;
-                       break;
-                   }
-               }
-
-               if (ok) {
-                   clenv->index = mnode;
-                   clenv->messages = rows;
-                   clenv->newmessages = clenv->messages;
-                   clenv->newsize = clenv->size;
-               } else
-                   free(mnode);
-
-           } else
-               DEBUG_PRINTF("do_buildindex",
-                       "malloc(%d)", rows * sizeof(msgnode));
-       } else
+    bool       ok = FALSE;
+    msgnode    *mnode;
+    PGresult   *pgres;
+    int                rows, i;
+    const char *params[2];
+
+    params[0] = clenv->mailbox;
+    params[1] = NULL;
+    if ((pgres = PQexecParams(clenv->pgconn,
+           "SELECT id,size,file FROM mail WHERE box=$1", 1, NULL, params,
+           NULL, NULL, 0)) != NULL) {
+       rows = PQntuples(pgres);
+       /* Allocate array and fill it */
+       if ((mnode = (msgnode *)malloc(rows * sizeof(msgnode))) != NULL) {
            ok = TRUE;
+           clenv->size = 0;
+           for (i = 0; i < rows; i++) {
+               mm_strcpy(mnode[i].id, PQgetvalue(pgres, i, 0));
+               mnode[i].size = atol(PQgetvalue(pgres, i, 1));
+               (void) snprintf(mnode[i].file, 255, "%s/%s", CONF.MAIL_DIR,
+                       PQgetvalue(pgres, i, 2));
+               clenv->size += mnode[i].size;
+               mnode[i].deleted = mnode[i].retreived = FALSE;
+           }
+           clenv->index = mnode;
+           clenv->messages = clenv->newmessages = rows;
+           clenv->newsize = clenv->size;
+       } else
+               ok = FALSE;
+       PQclear(pgres);
+    }
 
-       mmsql_free_result(mysqlres);
-    } else
-       DEBUG_PRINTF("do_buildindex", "mmsql_query(%s)", line);
-
-    return (ok);
+    return ok;
 }
 
 
-/* Loads a message body, either using mmap(2) for file storage mode, or a
- * MySQL query. This function along with do_message_free() are called by
- * do_retreive(), do_top() and do_page().
+/*
+ * Loads a message body, using mmap(2).
+ * This function along with do_message_free() are called by do_retreive(),
+ * do_top() and do_page().
  */
 static bool
 do_message_load(msgdata *mdata, msgnode *mnode)
 {
-#if defined(MMMAIL_MYSQL)
-
-    char line[1024];
-    MYSQL_RES *mysqlres;
-    MYSQL_ROW *row;
-    unsigned long *lengths;
-
-    snprintf(line, 1024, "SELECT mail_data FROM mail WHERE mail_id=%s",
-           mnode->id);
-    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)) == 1) {
-                   lengths = mysql_fetch_lengths(mysqlres);
-                   if (row[0] != NULL) {
-                       mdata->internal = mysqlres;
-                       mdata->body = (char *)row[0];
-                       mdata->size = lengths[0];
-
-                       return TRUE;
-                   } else
-                       DEBUG_PRINTF("do_message_load", "row[0] == NULL");
-               } else
-                   DEBUG_PRINTF("do_message_load", "mysql_num_fields != 1");
-           } else
-               DEBUG_PRINTF("do_message_load", "mysql_fetch_row()");
-       } else
-           DEBUG_PRINTF("do_message_load", "mysql_num_rows != 1");
-       mmsql_free_result(mysqlres);
-    } else
-       DEBUG_PRINTF("do_message_load", "mmsql_query(%s)", line);
-
-#elif defined(MMMAIL_FILE)
-
     int fd;
 
     if ((fd = open(mnode->file, O_RDONLY)) != -1) {
@@ -1263,6 +1161,7 @@ do_message_load(msgdata *mdata, msgnode *mnode)
            mdata->size = (size_t)st.st_size;
            if ((mdata->internal = mmap(NULL, mdata->size, PROT_READ,
                            MAP_FILE | MAP_PRIVATE, fd, 0)) != MAP_FAILED) {
+               (void) madvise(mdata->internal, mdata->size, MADV_SEQUENTIAL);
                mdata->body = mdata->internal;
                (void) close(fd);
 
@@ -1277,8 +1176,6 @@ do_message_load(msgdata *mdata, msgnode *mnode)
        DEBUG_PRINTF("do_message_load", "open(%s) == %s", mnode->file,
                strerror(errno));
 
-#endif
-
     mdata->internal = NULL;
     mdata->body = NULL;
     mdata->size = 0;
@@ -1290,11 +1187,7 @@ do_message_load(msgdata *mdata, msgnode *mnode)
 static void
 do_message_free(msgdata *mdata)
 {
-#if defined(MMMAIL_MYSQL)
-    mmsql_free_result(mdata->internal);
-#elif defined(MMMAIL_FILE)
     (void) munmap(mdata->internal, mdata->size);
-#endif
 
     mdata->internal = NULL;
     mdata->body = NULL;
@@ -1445,55 +1338,35 @@ do_page(fdbuf *fdb, msgnode *mnode, long page, long lines)
 static bool
 do_update(clientenv *clenv)
 {
-    char line[1024];
-    bool ok = TRUE;
-    long i, t, messages, size;
-    msgnode *mnode;
+    bool       ok = TRUE;
+    long       i, t;
+    msgnode    *mnode;
+    PGresult   *pgres;
 
     if ((t = clenv->messages) > 0) {
-
        /* Deleted messages and bytes */
-       messages = 0;
-       size = 0;
-
        mnode = clenv->index;
-       /* Delete messages that were marked, use a lock between our two calls
-        * which prevents mmsmtpd or other threads to interfere in-between
+
+       /*
+        * Delete messages that were marked
         */
-       mmsql_glock("mmmail_boxmail");
        for (i = 0; i < t; i++) {
+           const char  *params[2];
+
+           params[1] = NULL;
            if (mnode[i].deleted) {
-               snprintf(line, 1023, "DELETE FROM mail WHERE mail_id=%s",
-                       mnode[i].id);
-               if (!mmsql_command(line, mm_strlen(line))) {
-                   DEBUG_PRINTF("do_update", "mmsql_command(%s)", line);
+               params[0] = mnode[i].id;
+               if ((pgres = PQexecParams(clenv->pgconn,
+                       "DELETE FROM mail WHERE id=$1", 1, NULL, params, NULL,
+                       NULL, 0)) != NULL)
+                   PQclear(pgres);
+               else
                    ok = FALSE;
-                   break;
-               } else {
-#if defined(MMMAIL_FILE)
-                   /* Also unlink associated file */
-                   if (unlink(mnode[i].file) == -1)
-                       mmsyslog(0, LOGLEVEL, "unlink(%s) == %s",
-                               mnode[i].file, strerror(errno));
-#endif
-                   messages++;
-                   size += mnode[i].size;
-               }
            }
        }
-       if (messages > 0 && size > 0) {
-           /* Update mailbox size */
-           snprintf(line, 1023,
-                   "UPDATE box SET box_size=box_size-%ld,"
-                   "box_msgs=box_msgs-%ld WHERE box_address='%s'",
-                   size, messages, clenv->mailbox);
-           if (!mmsql_command(line, mm_strlen(line)))
-               DEBUG_PRINTF("do_update", "mmsql_command(%s)", line);
-       }
-       mmsql_gunlock("mmmail_boxmail");
     }
 
-    return (ok);
+    return ok;
 }
 
 
@@ -1576,9 +1449,6 @@ handleclient(unsigned long id, int fd, clientlistnode *clientlnode,
        /* Allocate our clientenv to share with state functions */
        if ((clenv = alloc_clientenv()) != NULL) {
 
-           /* Query some configuration options from the MySQL server,
-            * such as max_rcpts, max_mesg_lines, max_mesg_size, hostname
-            */
            clenv->fdb = fdb;
            clenv->buffer = buffer;
            clenv->errors = 0;
index 7f5caff..26dc357 100644 (file)
@@ -1,7 +1,7 @@
-/* $Id: mmpop3d.h,v 1.17 2007/03/13 20:28:22 mmondor Exp $ */
+/* $Id: mmpop3d.h,v 1.18 2007/03/16 17:24:10 mmondor Exp $ */
 
 /*
- * Copyright (C) 2001-2004, Matthew Mondor
+ * Copyright (C) 2001-2007, Matthew Mondor
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
 
 #include <mm_pthread_sleep.h>
 
+#include <libpq-fe.h>
+
 
 
 
 /* DEFINITIONS */
 
 #define DAEMON_NAME    "mmpop3d"
-#define DAEMON_VERSION "mmmail-0.1.0/mmondor"
+#define DAEMON_VERSION "mmmail-0.2.0/mmondor"
 
 /* Negative states are used by the state swapper, others are real states */
 #define STATE_ERROR    -3
 #define ASYNC_CHECKPW  1
 
 /* Error registration macro */
-#define REGISTER_ERROR(x) do { \
-    (x)->errors++; \
-       if (CONF.DELAY_ON_ERROR) \
-           pthread_sleep((x)->errors); \
-} while(0)
+#define REGISTER_ERROR(x) do {                                         \
+    (x)->errors++;                                                     \
+       if (CONF.DELAY_ON_ERROR)                                        \
+           pthread_sleep((x)->errors);                                 \
+} while (/* CONSTCOND */0)
 
 
 
@@ -89,8 +91,8 @@
 /* We store config file read results in this structure */
 typedef struct config {
     char LOCK_PATH[256], CHROOT_DIR[256], PID_PATH[256], USER[32], GROUPS[256],
-       LOG_FACILITY[32], LISTEN_IPS[1024], SERVER_NAMES[1024], DB_HOST[64],
-       DB_USER[32], DB_PASSWORD[32], DB_DATABASE[32];
+       LOG_FACILITY[32], LISTEN_IPS[1024], SERVER_NAMES[1024], DB_INFO[1024],
+       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,
@@ -101,13 +103,8 @@ typedef struct config {
 /* For the messages list during the session */
 typedef struct msgnode {
     char id[24];                 /* ID in the database */
-#if defined(MMMAIL_FILE)
     char file[256];              /* Fullpath to message file */
-#elif defined(MMMAIL_MYSQL)
-#else
-#error "One of MMMAIL_FILE or MMMAIL_MYSQL myst be #defined!"
-#endif
-    long size;                   /* Message size in bytes */
+    long size;                   /* Message size in bytes */
     bool retreived, deleted;     /* Flags */
 } msgnode;
 
@@ -139,7 +136,8 @@ typedef struct clientenv {
     bool login;                        /* For all_quit() */
     struct iface *iface;       /* Current interface user connected through */
     struct async_clenv *aclenv;        /* Thread context for async_call() */
-    mmstat_t vstat, pstat;     /* mmstat(3) handles */
+    mmstat_t vstat, pstat;     /* Persistent mmstat(3) handles */
+    PGconn *pgconn;            /* Persistent db connection */
 } clientenv;
 
 /* Used for mmfd thread support delegation/abstraction */
@@ -217,6 +215,8 @@ static bool do_page(fdbuf *, msgnode *, long, long);
 static bool do_update(clientenv *);
 static bool msgwrite(fdbuf *, const void *, size_t);
 static bool reply(fdbuf *, bool, const char *, ...);
+static bool clenv_constructor(pnode_t *);
+static void clenv_destructor(pnode_t *);
 static clientenv *alloc_clientenv(void);
 static bool init_clientenv(clientenv *);
 static clientenv *free_clientenv(clientenv *);
index 3c9759e..28c205c 100644 (file)
@@ -1,7 +1,7 @@
-/* $Id: mmsmtpd.c,v 1.75 2007/03/13 20:28:22 mmondor Exp $ */
+/* $Id: mmsmtpd.c,v 1.76 2007/03/16 17:24:10 mmondor Exp $ */
 
 /*
- * Copyright (C) 2001-2004, Matthew Mondor
+ * Copyright (C) 2001-2007, Matthew Mondor
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -68,7 +68,6 @@
 #include <mmpool.h>
 #include <mmhash.h>
 #include <mmserver.h>
-#include <mmsql.h>
 #include <mmlog.h>
 #include <mmstr.h>
 #include <mmstring.h>
@@ -81,9 +80,9 @@
 
 
 
-MMCOPYRIGHT("@(#) Copyright (c) 2001-2004\n\
+MMCOPYRIGHT("@(#) Copyright (c) 2001-2007\n\
 \tMatthew Mondor. All rights reserved.\n");
-MMRCSID("$Id: mmsmtpd.c,v 1.75 2007/03/13 20:28:22 mmondor Exp $");
+MMRCSID("$Id: mmsmtpd.c,v 1.76 2007/03/16 17:24:10 mmondor Exp $");
 
 
 
@@ -198,7 +197,7 @@ static fdfuncs gfdf = {
 /*
  * Used to speed up VALID_ADDR_CHAR()
  */
-static const unsigned char valid_addr_char_table[256] = {
+static const int valid_addr_char_table[256] = {
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 
@@ -219,7 +218,7 @@ static const unsigned char valid_addr_char_table[256] = {
 /* 
  * And for VALID_HOST_CHAR()
  */
-static const unsigned char valid_addr_host_table[256] = {
+static const int valid_addr_host_table[256] = {
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 
@@ -257,7 +256,6 @@ main(int argc, char **argv)
     char *conf_file = "/usr/local/etc/mmsmtpd.conf";
     int ngids, ret = -1;
     long facility;
-    char *db_host;
     bool strlist;
     cres_t cres;
     carg_t *cargp;
@@ -270,10 +268,7 @@ main(int argc, char **argv)
        {CAT_STR, CAF_NONE, 1, 31, "LOG_FACILITY", CONF.LOG_FACILITY},
        {CAT_STR, CAF_NONE, 1, 1023, "SERVER_NAMES", CONF.SERVER_NAMES},
        {CAT_STR, CAF_NONE, 1, 1023, "LISTEN_IPS", CONF.LISTEN_IPS},
-       {CAT_STR, CAF_NONE, 1, 63, "DB_HOST", CONF.DB_HOST},
-       {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, 1023, "DB_INFO", CONF.DB_INFO},
        {CAT_STR, CAF_NONE, 1, 255, "MAIL_DIR", CONF.MAIL_DIR},
        {CAT_STR, CAF_NONE, 1, 255, "MMRELAYD_SOCKET_PATH",
            CONF.MMRELAYD_SOCKET_PATH},
@@ -339,13 +334,6 @@ main(int argc, char **argv)
        {async_resquery, sizeof(struct async_resquery_msg)},
        {NULL, 0}
     };
-    struct mmsql_threadsupport mmsqlfuncs = {
-       thread_mutex_create,
-       thread_mutex_destroy,
-       thread_mutex_lock,
-       thread_mutex_unlock,
-       pthread_sleep
-    };
     mmstat_t vstat;
     pthread_t hosts_table_thread = NULL;
     pthread_t mmmail_db_gc_thread = NULL;
@@ -360,10 +348,7 @@ main(int argc, char **argv)
     mm_strcpy(CONF.LOG_FACILITY, "LOG_AUTHPRIV");
     mm_strcpy(CONF.SERVER_NAMES, "smtp.localhost");
     mm_strcpy(CONF.LISTEN_IPS, "127.0.0.1");
-    mm_strcpy(CONF.DB_HOST, "localhost");
-    mm_strcpy(CONF.DB_USER, "mmmail");
-    mm_strcpy(CONF.DB_PASSWORD, "mmmailpassword");
-    mm_strcpy(CONF.DB_DATABASE, "mmmail");
+    mm_strcpy(CONF.DB_INFO, "dbname=mmmail");
     mm_strcpy(CONF.MAIL_DIR, "/var/mmmail-dir");
     mm_strcpy(CONF.MMRELAYD_SOCKET_PATH, "/var/run/mmrelayd.sock");
     CONF.ASYNC_PROCESSES = 3;
@@ -447,14 +432,11 @@ main(int argc, char **argv)
        printf("\nOne of following groups unknown: '%s'\n\n", CONF.GROUPS);
        exit(-1);
     }
-    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) {
@@ -465,7 +447,6 @@ main(int argc, char **argv)
            printf("\nMAIL_DIR not a directory: '%s'\n\n", CONF.MAIL_DIR);
            exit(-1);
        }
-#endif /* defined(MMMAIL_FILE) */
     }
 
     /* Finally init everything */
@@ -493,12 +474,6 @@ main(int argc, char **argv)
     mmstat(&vstat, STAT_DELETE, 0, "mmsmtpd|who|*");
     mmstat_transact(&vstat, FALSE);
     res_init();
-    if (!(mmsql_open(db_host, CONF.DB_USER, CONF.DB_PASSWORD,
-                   CONF.DB_DATABASE))) {
-       printf("\nCould not connect to MySQLd\n\n");
-       syslog(LOG_NOTICE, "* Could not connect to MySQLd");
-       exit(-1);
-    }
 
     make_daemon(CONF.PID_PATH, CONF.CHROOT_DIR);
 
@@ -510,9 +485,10 @@ main(int argc, char **argv)
 
     /* Allocate necessary pools */
     /* Client nodes */
-    pool_init(&clenv_pool, "clenv_pool", malloc, free, NULL, NULL,
-           sizeof(clientenv),
-           (65536 * CONF.ALLOC_BUFFERS) / sizeof(clientenv), 0, 0);
+    pool_init(&clenv_pool, "clenv_pool", malloc, free,
+           clientenv_constructor, clientenv_destructor,
+           sizeof(clientenv), (65536 * CONF.ALLOC_BUFFERS) / sizeof(clientenv),
+           0, 0);
     /* RCPT nodes */
     pool_init(&rcpt_pool, "rcpt_pool", malloc, free, NULL, NULL,
            sizeof(rcptnode),
@@ -523,7 +499,7 @@ main(int argc, char **argv)
            (16384 * CONF.ALLOC_BUFFERS) / sizeof(struct mutexnode), 0, 0);
 
     pthread_attr_init(&threadattr);
-    pthread_attr_setdetachstate(&threadattr, 0);
+    pthread_attr_setdetachstate(&threadattr, PTHREAD_CREATE_JOINABLE);
 
     /* Rate nodes */
     if (CONF.FLOOD_PROTECTION) {
@@ -534,7 +510,7 @@ main(int argc, char **argv)
        pthread_create(&hosts_table_thread, &threadattr, hosts_expire_thread,
                       NULL);
     }
-    /* Launch box directories cleaning thread */
+    /* Launch files gc cleaning thread */
     pthread_create(&mmmail_db_gc_thread, &threadattr, db_gc_thread, NULL);
     /* mmstr nodes */
     strlist = mmstrinit(malloc, free, 65536 * CONF.ALLOC_BUFFERS);
@@ -547,7 +523,6 @@ main(int argc, char **argv)
        thread_init();
        fdbcinit(&gfdf, &fdbc, CONF.GBANDWIDTH_IN * 1024,
                CONF.GBANDWIDTH_OUT * 1024);
-       mmsql_init(&mmsqlfuncs);
 
        tcp_server("402 Server too busy, try again\r\n",
                CONF.SERVER_NAMES, CONF.LISTEN_IPS, uid, gids, ngids,
@@ -557,8 +532,6 @@ main(int argc, char **argv)
 
        mmfreegidarray(gids);
        ret = 0;
-       mmsql_close();
-       mmsql_exit();
     } else {
        printf("\nOut of memory\n\n");
        syslog(LOG_NOTICE, "* Out of memory");
@@ -747,8 +720,7 @@ all_mail(clientenv *clenv)
                 * If so, we also want to make sure not to perform any type
                 * of envelope based filtering for this post.
                 */
-               valid = check_nofrom(clenv->c_ipaddr, clenv->c_hostname);
-               if (valid)
+               if ((valid = check_nofrom(clenv)))
                    *addr = '\0';
                clenv->nofrom = TRUE;
            } else {
@@ -829,16 +801,15 @@ all_rcpt(clientenv *clenv)
      */
     valid = FALSE;
     (void) mm_strcpy(foraddr, addr);
-    if (!(valid = local_address(&boxinfo, addr))) {
-       if (check_alias(addr)) {
-           if (!(valid = local_address(&boxinfo, addr)))
+    if (!(valid = local_address(clenv, &boxinfo, addr))) {
+       if (check_alias(clenv, addr)) {
+           if (!(valid = local_address(clenv, &boxinfo, addr)))
                mmsyslog(0, LOGLEVEL, "Invalid alias address (%s)",
                        addr);
        }
     }
     if (!valid)
        reason = RCPT_UNKNOWN;
-#if defined(MMMAIL_FILE)
     if (CONF.RELAYING && !valid) {
        /* Address is not local. If relaying is allowed, we must be
         * able to verify that the address indeed belongs to a
@@ -863,7 +834,6 @@ all_rcpt(clientenv *clenv)
            relay = TRUE;
        }
     }
-#endif /* defined(MMMAIL_FILE) */
     if (!valid) {
        switch (reason) {
        case RCPT_RELAY:
@@ -891,7 +861,8 @@ all_rcpt(clientenv *clenv)
         * with an empty FROM address from allowed servers
         */
        if (boxinfo.filter && !clenv->nofrom) {
-           if (!box_filter_allow(addr, clenv->from, boxinfo.filter_type)) {
+           if (!box_filter_allow(clenv, addr, clenv->from,
+                       boxinfo.filter_type)) {
                reason = RCPT_FILTER;
                if (CONF.STATFAIL_FILTER)
                    mmstat(&clenv->pstat, STAT_UPDATE, 1,
@@ -901,8 +872,8 @@ all_rcpt(clientenv *clenv)
            }
        }
        /* Make sure mailbox quota limits are respected */
-       if (!((boxinfo.size <= boxinfo.max_size) &&
-                   (boxinfo.msgs <= boxinfo.max_msgs))) {
+       if (!((boxinfo.size < boxinfo.max_size) &&
+                   (boxinfo.msgs < boxinfo.max_msgs))) {
            mmsyslog(0, LOGLEVEL, "%s mailbox full (%ld,%ld %ld,%ld)",
                    addr, boxinfo.max_size, boxinfo.size, boxinfo.max_msgs,
                    boxinfo.msgs);
@@ -916,6 +887,7 @@ all_rcpt(clientenv *clenv)
 
     /* Make sure that we only allow one RCPT per mailbox (alias already
      * redirected to it)
+     * XXX We probably should use a real hash table for RCPTs
      */
     {
        register rcptnode *rnode;
@@ -1145,6 +1117,38 @@ reply(fdbuf *fdb, int code, bool cont, const char *fmt, ...)
 }
 
 
+static bool
+clientenv_constructor(pnode_t *pn)
+{
+    clientenv  *clenv = (clientenv *)pn;
+
+    mmstat_init(&clenv->vstat, TRUE, TRUE);
+    mmstat_init(&clenv->pstat, TRUE, FALSE);
+
+    /* XXX Performance enhancement but requires thread safety
+    if ((clenv->pgconn = PQconnectdb(CONF.DB_INFO)) == NULL) {
+       mmsyslog(LOGLEVEL, 0, "PQconnectdb()");
+       return FALSE;
+    }
+    */
+
+    return TRUE;
+}
+
+static void
+clientenv_destructor(pnode_t *pn)
+{
+    /* XXX Performance enhancement but requires thread safety
+    clientenv  *clenv = (clientenv *)pn;
+
+    if (clenv->pgconn != NULL) {
+       PQfinish(clenv->pgconn);
+       clenv->pgconn = NULL;
+    }
+    */
+}
+
+
 /* Allocate and prepare a clenv. Returns NULL on error */
 static clientenv *
 alloc_clientenv(void)
@@ -1155,9 +1159,10 @@ alloc_clientenv(void)
     clenv = (clientenv *)pool_alloc(&clenv_pool, TRUE);
     pthread_mutex_unlock(&clenv_lock);
 
-    if (clenv != NULL) {
-       mmstat_init(&clenv->vstat, TRUE, TRUE);
-       mmstat_init(&clenv->pstat, TRUE, FALSE);
+    /* XXX for now */
+    if ((clenv->pgconn = PQconnectdb(CONF.DB_INFO)) == NULL) {
+       syslog(LOG_NOTICE, "alloc_clientenv() - PQconnectdb()");
+       return free_clientenv(clenv);
     }
 
     return (clenv);
@@ -1190,6 +1195,10 @@ free_clientenv(clientenv *clenv)
        mmstrfree(clenv->from);
     empty_rcpts(&clenv->rcpt);
 
+    /* XXX for now */
+    if (clenv->pgconn != NULL)
+       PQfinish(clenv->pgconn);
+
     pthread_mutex_lock(&clenv_lock);
     pool_free((pnode_t *)clenv);
     pthread_mutex_unlock(&clenv_lock);
@@ -1222,15 +1231,14 @@ empty_rcpts(list_t *lst)
  * map it to the real address to redirect to, replacing supplied address.
  * The addr char array must at least be 64 bytes. Returns FALSE if no alias
  * exist for the address, or TRUE on success.
- * XXX Could possibly use an async function, if we allow the async processes
- * to connect to MySQLd.
  */
 static bool
-check_alias(char *addr)
+check_alias(clientenv *clenv, char *addr)
 {
-    bool res = FALSE;
-    char oaddr[64], query[1024], *user, *domain;
-    MYSQL_RES *mysqlres;
+    bool       res = FALSE;
+    char       oaddr[64], *user, *domain;
+    PGresult   *pgres;
+    const char *params[2];
 
     /* We know that the address is valid, since it has been verified already.
      * Copy it to not modify our supplied address, and to keep a backup of the
@@ -1241,39 +1249,31 @@ check_alias(char *addr)
     for (user = oaddr, domain = oaddr; *domain != '@'; domain++) ;
     *domain++ = '\0';
 
-    snprintf(query, 1023, "SELECT alias_pattern,alias_box FROM alias "
-           "WHERE alias_domain='%s'", domain);
-    if ((mysqlres = mmsql_query(query, mm_strlen(query))) != NULL) {
-       if ((mysql_num_rows(mysqlres)) > 0) {
-           char pat[64];
-           int cur = 0, max = -1;
-           MYSQL_ROW *row;
-           unsigned long *lengths;
-
-           /* Find best match */
-           while ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres))
-                   != NULL) {
-               lengths = mysql_fetch_lengths(mysqlres);
-               if (row[0] != NULL && row[1] != NULL) {
-                   mm_memcpy(pat, row[0], lengths[0]);
-                   pat[lengths[0]] = '\0';
-                   if ((cur = best_match(user, pat)) != -1) {
-                       if (cur > max) {
-                           /* Matches better, remember this one */
-                           max = cur;
-                           mm_memcpy(addr, row[1], lengths[1]);
-                           addr[lengths[1]] = '\0';
-                       }
-                   }
+    params[0] = domain;
+    params[1] = NULL;
+    if ((pgres = PQexecParams(clenv->pgconn,
+           "SELECT pattern,box FROM alias WHERE domain=$1", 1, NULL, params, NULL,
+           NULL, 0)) != NULL) {
+       int             i, t, cur = 0, max = -1;
+       const char      *a = NULL;
+
+       for (i = 0, t = PQntuples(pgres); i < t; i++) {
+           if ((cur = best_match(user, PQgetvalue(pgres, i, 0))) != -1) {
+               if (cur > max) {
+                   /* Better match, remember this one */
+                   max = cur;
+                   a = PQgetvalue(pgres, i, 1);
                }
            }
-           if (max > -1)
-               res = TRUE;
        }
-       mysqlres = mmsql_free_result(mysqlres);
+       if (max > -1) {
+           (void) mm_strcpy(addr, a);
+           res = TRUE;
+       }
+       PQclear(pgres);
     }
 
-    return (res);
+    return res;
 }
 
 
@@ -1281,46 +1281,38 @@ check_alias(char *addr)
  * of both matched an entry.
  */
 static bool
-check_nofrom(const char *addr, const char *host)
+check_nofrom(clientenv *clenv)
 {
-    bool res = FALSE;
-    MYSQL_RES *mysqlres;
+    bool       res = FALSE;
+    PGresult   *pgres;
 
-    if (addr == NULL && host == NULL)
+    if (clenv->c_ipaddr == NULL && clenv->c_hostname == NULL)
        return (FALSE);
 
-    if ((mysqlres = mmsql_query("SELECT nofrom_pattern FROM nofrom", -1))
+    if ((pgres = PQexec(clenv->pgconn, "SELECT pattern FROM nofrom"))
            != NULL) {
-       if ((mysql_num_rows(mysqlres)) > 0) {
-           MYSQL_ROW *row;
-           unsigned long *lengths;
-
-           while ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres)) != NULL) {
-               lengths = mysql_fetch_lengths(mysqlres);
-               if (row[0] != NULL) {
-                   char pat[64];
-
-                   mm_memcpy(pat, row[0], lengths[0]);
-                   pat[lengths[0]] = '\0';
-                   if (addr != NULL) {
-                       if ((best_match(addr, pat)) != -1) {
-                           res = TRUE;
-                           break;
-                       }
-                   }
-                   if (host != NULL) {
-                       if ((best_match(host, pat)) != -1) {
-                           res = TRUE;
-                           break;
-                       }
-                   }
+       int     i, t;
+       char    *pat;
+
+       for (i = 0, t = PQntuples(pgres); i < t; i++) {
+           pat = PQgetvalue(pgres, i, 0);
+           if (clenv->c_ipaddr != NULL) {
+               if ((best_match(clenv->c_ipaddr, pat)) != -1) {
+                   res = TRUE;
+                   break;
+               }
+           }
+           if (clenv->c_hostname != NULL) {
+               if ((best_match(clenv->c_hostname, pat)) != -1) {
+                   res = TRUE;
+                   break;
                }
            }
        }
-       mysqlres = mmsql_free_result(mysqlres);
+       PQclear(pgres);
     }
 
-    return (res);
+    return res;
 }
 
 
@@ -1380,62 +1372,34 @@ best_match(const char *str, const char *pat)
 
 
 /* Returns FALSE if this address doesn't exist in our local mailboxes.
- * Otherwise it returns information about the mailbox via supplied pointers.
+ * Otherwise it populates boxinfo structure with information about the
+ * mailbox.
  */
 static bool
-local_address(struct box_info *boxinfo, const char *address)
+local_address(clientenv *clenv, struct box_info *boxinfo, const char *address)
 {
     bool               res = FALSE;
-    char               line[1024];
-    MYSQL_RES          *mysqlres;
-    MYSQL_ROW          *row;
-    unsigned int       fields;
-    unsigned long      *lengths;
-
-    /* Query mysql to see if this address exists, and get limits/status */
-    (void) snprintf(line, 1023,
-            "SELECT box_max_size,box_size,box_max_msgs,box_msgs,box_filter,"
-            "box_filter_type FROM box WHERE box_address='%s'", address);
-
-    if ((mysqlres = mmsql_query(line, mm_strlen(line))) != NULL) {
-
-       if ((mysql_num_rows(mysqlres)) == 1
-           && (row = (MYSQL_ROW *)mysql_fetch_row(mysqlres)) != NULL) {
-           if ((fields = mysql_num_fields(mysqlres)) == 6) {
-               lengths = mysql_fetch_lengths(mysqlres);
-               if (row[0] != NULL && row[1] != NULL && row[2] != NULL &&
-                       row[3] != NULL && row[4] != NULL && row[5] != NULL) {
-                   int v;
-
-                   mm_memcpy(line, row[0], lengths[0]);
-                   line[lengths[0]] = '\0';
-                   boxinfo->max_size = atol(line);
-                   mm_memcpy(line, row[1], lengths[1]);
-                   line[lengths[1]] = '\0';
-                   boxinfo->size = atol(line);
-                   mm_memcpy(line, row[2], lengths[2]);
-                   line[lengths[2]] = '\0';
-                   boxinfo->max_msgs = atol(line);
-                   mm_memcpy(line, row[3], lengths[3]);
-                   line[lengths[3]] = '\0';
-                   boxinfo->msgs = atol(line);
-                   mm_memcpy(line, row[4], lengths[4]);
-                   line[lengths[4]] = '\0';
-                   v = atol(line);
-                   boxinfo->filter = (v == 1 ? TRUE : FALSE);
-                   mm_memcpy(line, row[5], lengths[5]);
-                   line[lengths[5]] = '\0';
-                   boxinfo->filter_type = *line;
-                   res = TRUE;
-               } else
-                   DEBUG_PRINTF("local_address", "row[x] == NULL");
-           } else
-               DEBUG_PRINTF("local_address", "mysql_num_fields()");
+    PGresult           *pgres;
+    const char         *params[2];
+
+    params[0] = address;
+    params[1] = NULL;
+    if ((pgres = PQexecParams(clenv->pgconn,
+           "SELECT max_size,size,max_msgs,msgs,filter,filter_type FROM box "
+           "WHERE address=$1", 1, NULL, params, NULL, NULL, 0)) != NULL) {
+       if (PQntuples(pgres) == 1) {
+           boxinfo->max_size = atol(PQgetvalue(pgres, 0, 0));
+           boxinfo->size = atol(PQgetvalue(pgres, 0, 1));
+           boxinfo->max_msgs = atol(PQgetvalue(pgres, 0, 2));
+           boxinfo->msgs = atol(PQgetvalue(pgres, 0, 3));
+           boxinfo->filter = (*(PQgetvalue(pgres, 0, 4)) == 't' ?
+                   TRUE : FALSE);
+           boxinfo->filter_type = (*(PQgetvalue(pgres, 0, 5)) == 't' ?
+                   TRUE : FALSE);
+           res = TRUE;
        }
-
-       mysqlres = mmsql_free_result(mysqlres);
-    } else
-       DEBUG_PRINTF("local_address", "mmsql_query(%s)", line);
+       PQclear(pgres);
+    }
 
     return res;
 }
@@ -1443,46 +1407,31 @@ local_address(struct box_info *boxinfo, const char *address)
 
 /* Verifies if mailbox <toaddr> filters allow <fromaddr> to post */
 static bool
-box_filter_allow(const char *toaddr, const char *fromaddr, char filter_type)
+box_filter_allow(clientenv *clenv, const char *toaddr, const char *fromaddr,
+       bool filter_type)
 {
     bool       res;
-    char       query[1024];
-    MYSQL_RES  *mysqlres;
+    PGresult   *pgres;
+    const char *params[2];
 
     /* Default return code depends on filter type */
-    res = (filter_type == 'A' ? TRUE : FALSE);
-
-    snprintf(query, 1023,
-           "SELECT filter_pattern FROM filter WHERE filter_address='%s'",
-           toaddr);
-    if ((mysqlres = mmsql_query(query, mm_strlen(query))) != NULL) {
-       if ((mysql_num_rows(mysqlres)) > 0) {
-           char                pat[64];
-           MYSQL_ROW           *row;
-           unsigned long       *lengths;
-
-           /*
-            * Filter types are 'A' (allow by default) and
-            * 'D' (deny by * default).
-            */
-           while ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres)) != NULL) {
-               lengths = mysql_fetch_lengths(mysqlres);
-               if (row[0] != NULL) {
-                   (void) mm_memcpy(pat, row[0], lengths[0]);
-                   pat[lengths[0]] = '\0';
-                   if (best_match(fromaddr, pat) != -1) {
-                       /* Inverse return code and break */
-                       res = !res;
-                       break;
-                   }
-               }
+    res = filter_type;
+
+    params[0] = toaddr;
+    params[1] = NULL;
+    if ((pgres = PQexecParams(clenv->pgconn,
+           "SELECT pattern FROM filter WHERE address=$1", 1, NULL, params,
+           NULL, NULL, 0)) != NULL) {
+       int     i, t;
+
+       for (i = 0, t = PQntuples(pgres); i < t; i++) {
+           if (best_match(fromaddr, PQgetvalue(pgres, i, 0)) != -1) {
+               res = !res;
+               break;
            }
-
-       } else
-           DEBUG_PRINTF("box_filter_allow", "mysql_num_rows()");
-       mysqlres = mmsql_free_result(mysqlres);
-    } else
-       DEBUG_PRINTF("box_filter_allow", "mmsql_query(%s)", query);
+       }
+       PQclear(pgres);
+    }
 
     return res;
 }
@@ -1854,28 +1803,9 @@ do_data(clientenv *clenv)
        break;
     }
 
-    if (ok) {
-
-       /* 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 defined(MMMAIL_MYSQL)
-
-       ok = do_data_mysql(clenv, fdbrb);
-
-#elif defined(MMMAIL_FILE)
-
+    if (ok)
        ok = do_data_file(clenv, fdbrb);
 
-#else
-#error "One of MMMAIL_MYSQL or MMMAIL_FILE must be #defined!"
-#endif
-
-    }
-
     fdbfreebuf(&fdbrb);        /* Internally only frees if not already freed */
     if (ok)
        err = DATA_OK;
@@ -1906,28 +1836,6 @@ do_data_received(char *line, size_t len, clientenv *clenv, rcptnode *rnode,
     return mm_strlen(line);
 }
 
-/* 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.
@@ -1961,106 +1869,6 @@ do_data_stats(clientenv *clenv, rcptnode *rnode, size_t len)
 }
 
 
-#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, 255,
-                   "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)
-
 /* Returns TRUE if the client address/hostname is allowed to relay messages
  * for non-local addresses, or FALSE otherwise, with reason set to either
  * RCPT_UNKNOWN (destination address on a locally handled domain but
@@ -2072,9 +1880,8 @@ bool
 address_relay_allow(clientenv *clenv, int *reason, const char *addr)
 {
     bool       res = TRUE;
-    char       query[1024];
     const char *domain;
-    MYSQL_RES  *mysqlres;
+    PGresult   *pgres;
 
     /* Is address to a local domain but unknown to us? */
 
@@ -2083,32 +1890,22 @@ address_relay_allow(clientenv *clenv, int *reason, const char *addr)
      */
     for (domain = addr; *domain != '@'; domain++) ;
     domain++;
+
     /* Query database entries and search for any matching pattern. */
-    (void) snprintf(query, 1023, "SELECT relaylocal_pattern FROM relaylocal");
-    if ((mysqlres = mmsql_query(query, mm_strlen(query))) != NULL) {
-       if ((mysql_num_rows(mysqlres)) > 0) {
-           char                pat[64];
-           MYSQL_ROW           *row;
-           unsigned long       *lengths;
-
-           while ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres)) != NULL) {
-               lengths = mysql_fetch_lengths(mysqlres);
-               if (row[0] != NULL) {
-                   mm_memcpy(pat, row[0], lengths[0]);
-                   pat[lengths[0]] = '\0';
-                   if (best_match(domain, pat) != -1) {
-                       res = FALSE;
-                       *reason = RCPT_UNKNOWN;
-                       break;
-                   }
-               }
-           }
-       } else
-           DEBUG_PRINTF("address_relay_allow", "mysql_num_rows()");
-       mysqlres = mmsql_free_result(mysqlres);
-    } else
-       DEBUG_PRINTF("address_relay_allow", "mmsql_query(%s)", query);
+    if ((pgres = PQexec(clenv->pgconn, "SELECT pattern FROM relaylocal"))
+           != NULL) {
+       int     i, t;
 
+       for (i = 0, t = PQntuples(pgres); i < t; i++) {
+           if (best_match(domain, PQgetvalue(pgres, i, 0)) != -1) {
+               res = FALSE;
+               *reason = RCPT_UNKNOWN;
+               break;
+           }
+       }
+       PQclear(pgres);
+    }
     /* Return with error immediately if address is locally handled */
     if (!res)
        return res;
@@ -2117,40 +1914,30 @@ address_relay_allow(clientenv *clenv, int *reason, const char *addr)
      * allowed to relay messages through us? Verify via the client's IP
      * address and/or hostname.
      */
-
     res = FALSE;
 
-    (void) snprintf(query, 1023, "SELECT relayfrom_pattern FROM relayfrom");
-    if ((mysqlres = mmsql_query(query, mm_strlen(query))) != NULL) {
-       if ((mysql_num_rows(mysqlres)) > 0) {
-           char                pat[64];
-           MYSQL_ROW           *row;
-           unsigned long       *lengths;
-
-           while ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres)) != NULL) {
-               lengths = mysql_fetch_lengths(mysqlres);
-               if (row[0] != NULL) {
-                   mm_memcpy(pat, row[0], lengths[0]);
-                   pat[lengths[0]] = '\0';
-                   if (clenv->c_ipaddr != NULL) {
-                       if (best_match(clenv->c_ipaddr, pat) != -1) {
-                           res = TRUE;
-                           break;
-                       }
-                   }
-                   if (clenv->c_hostname != NULL) {
-                       if (best_match(clenv->c_hostname, pat) != -1) {
-                           res = TRUE;
-                           break;
-                       }
-                   }
+    if ((pgres = PQexec(clenv->pgconn, "SELECT pattern FROM relayfrom"))
+           != NULL) {
+       int     i, t;
+       char    *pat;
+
+       for (i = 0, t = PQntuples(pgres); i < t; i++) {
+           pat = PQgetvalue(pgres, i, 0);
+           if (clenv->c_ipaddr != NULL) {
+               if (best_match(clenv->c_ipaddr, pat) != -1) {
+                   res = TRUE;
+                   break;
                }
            }
-       } else
-           DEBUG_PRINTF("address_relay_allow", "mysql_num_rows()");
-       mysqlres = mmsql_free_result(mysqlres);
-    } else
-       DEBUG_PRINTF("address_relay_allow", "mmsql_query(%s)", query);
+           if (clenv->c_hostname != NULL) {
+               if (best_match(clenv->c_hostname, pat) != -1) {
+                   res = TRUE;
+                   break;
+               }
+           }
+       }
+       PQclear(pgres);
+    }
 
     if (!res)
        *reason = RCPT_RELAY;
@@ -2178,7 +1965,7 @@ iso_time(char *str)
 /* Saves a message to disk, into the <box> directory, creating the directory
  * if needed. It ensures to create a unique filename in that directory. The
  * directory and the file will be located into MAIL_DIR. Locks should be held
- * as necessary before calling this function is atomic behavior is required
+ * as necessary before calling this function as atomic behavior is required
  * among other tasks. <recvline> consists of the "Received:" header which will
  * be written first, followed by the data held in the <fdbrb> buffer. The
  * fullpath to the created filename will be stored into supplied <path>, which
@@ -2191,6 +1978,7 @@ message_write(char *path, const char *recvline, size_t recvlen,
     bool       ok = FALSE;
     char       filetime[16];
     int                i, fd;
+    u_int32_t  r;
 
     fd = -1;
 
@@ -2209,11 +1997,14 @@ message_write(char *path, const char *recvline, size_t recvlen,
      */
     iso_time(filetime);
     for (i = 0; i < 64; i++) {
+       r = (u_int32_t)random();
        (void) snprintf(path, 255, "%s/%s/%s.%08X", CONF.MAIL_DIR,
-                       box, filetime, (u_int32_t)random());
+                       box, filetime, r);
        if ((fd = open(path, O_CREAT | O_EXCL | O_WRONLY, 00640)) != -1)
            break;
     }
+    /* Write final relative path in supplied buffer */
+    (void) snprintf(path, 255, "%s/%s.%08X", box, filetime, r);
 
     /* Successfully created a new unique file, save message data.
      * We also verify the return value of close(2), but are not calling
@@ -2229,11 +2020,16 @@ message_write(char *path, const char *recvline, size_t recvlen,
            mmsyslog(0, LOGLEVEL, "write()/close()|(%s) == %s",
                    path, strerror(errno));
            (void) close(fd);
+           (void) snprintf(path, 255, "%s/%s/%s.%08X", CONF.MAIL_DIR, box,
+                           filetime, r);
            (void) unlink(path);
        }
     } else
        mmsyslog(0, LOGLEVEL, "open(%s) == %s", path, strerror(errno));
 
+    if (!ok)
+       *path = '\0';
+
     return ok;
 }
 
@@ -2274,58 +2070,39 @@ static bool
 do_data_queue_box(clientenv *clenv, const char *recvline, size_t recvlen,
        struct fdbrb_buffer *fdbrb, rcptnode *rnode)
 {
-    char       line[1024], path[256];
-    bool       ok = TRUE;
+    char       path[256], v[16];
+    PGresult   *pgres;
+    const char *params[4];
 
     /* 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.
+     * XXX We don't use this type of locking anymore for now.  A file advisory
+     * lock might be wanted though, or a PostgreSQL advisory lock.
      */
-    mmsql_glock("mmmail_boxmail");
 
-    if (message_write(path, recvline, recvlen, fdbrb, rnode->address)) {
-       /* 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.
-        */
-       (void) 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))) {
-           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.
-                */
-               (void) snprintf(line, 1023,
-                       "DELETE FROM mail WHERE mail_id=%llu", id);
-               (void) mmsql_command(line, mm_strlen(line));
-           }
-       } else {
-           mmsyslog(0, LOGLEVEL, "mmsql_command(%s)", line);
-           ok = FALSE;
-       }
-       /* If anything failed, delete stored message file. */
-       if (!ok)
-           (void) unlink(path);
-    } else
-       ok = FALSE;
+    if (!message_write(path, recvline, recvlen, fdbrb, rnode->address))
+       return FALSE;
 
-    /* We can finally safely release the global lock */
-    mmsql_gunlock("mmmail_boxmail");
+    (void) snprintf(v, 15, "%ld", (long)fdbrb->current + recvlen);
+    params[0] = rnode->address;
+    params[1] = v;
+    params[2] = path;
+    params[3] = NULL;
+    if ((pgres = PQexecParams(clenv->pgconn,
+           "INSERT INTO mail (box,size,file) VALUES($1,$2,$3)", 3, NULL,
+           params, NULL, NULL, 0)) != NULL)
+       PQclear(pgres);
+    else {
+       syslog(LOG_NOTICE, "do_date_queue_box() - PQexecParams()");
+       (void) unlink(path);
 
-    /* If everything successful, update mmstat statistics */
-    if (ok)
-       do_data_stats(clenv, rnode, fdbrb->current + recvlen);
+       return FALSE;
+    }
 
-    return ok;
+    do_data_stats(clenv, rnode, fdbrb->current + recvlen);
+
+    return TRUE;
 }
 
 /* Queue a message for relaying */
@@ -2333,13 +2110,15 @@ static bool
 do_data_queue_relay(clientenv *clenv, const char *recvline, size_t recvlen,
        struct fdbrb_buffer *fdbrb, rcptnode *rnode)
 {
-    char       line[1024], path[256], *user, *domain, *restore;
+    char       path[256], *user, *domain, *restore, v[16];
     bool       ok = TRUE;
+    PGresult   *pgres;
+    const char *params[7];
 
     /* This lock allows to maintain atomicity between the message file and
      * its corresponding database entry, between mmsmtpd(8) and mmrelayd(8).
+     * XXX We no longer currently use a lock.
      */
-    mmsql_glock("mmmail_relayqueue");
 
     /* We know that the address is valid in the rcpt node, separate it into
      * user and domain strings.
@@ -2351,16 +2130,22 @@ do_data_queue_relay(clientenv *clenv, const char *recvline, size_t recvlen,
 
     if (message_write(path, recvline, recvlen, fdbrb, "relayqueue")) {
        /* Message file saved successfully, add corresponding DB entry */
-       (void) snprintf(line, 1023,
-               "INSERT INTO relayqueue (relayqueue_from,relayqueue_ipaddr,"
-               "relayqueue_todomain,relayqueue_touser,relayqueue_size,"
-               "relayqueue_file,relayqueue_queued) "
-               "VALUES('%s','%s','%s','%s','%ld','%s',NOW())",
-               clenv->from, clenv->c_ipaddr, domain, user,
-               (long)fdbrb->current + recvlen, path);
-       if (!mmsql_command(line, mm_strlen(line))) {
-           /* SQL request failed, delete saved file */
-           mmsyslog(0, LOGLEVEL, "mmsql_command(%s)", line);
+       (void) snprintf(v, 15, "%ld", (long)fdbrb->current + recvlen);
+       params[0] = clenv->from;
+       params[1] = clenv->c_ipaddr;
+       params[2] = domain;
+       params[3] = user;
+       params[4] = v;
+       params[5] = path;
+       params[6] = NULL;
+       if ((pgres = PQexecParams(clenv->pgconn, "INSERT INTO relayqueue "
+               "(\"from\",ipaddr,todomain,touser,size,file) "
+               "VALUES($1,$2,$3,$4,$5,$6)", 6, NULL, params, NULL, NULL, 0))
+               != NULL)
+           PQclear(pgres);
+       else {
+           syslog(LOG_NOTICE, "do_data_queue_relay() - PQexecParams()");
+           (void) snprintf(path, 255, "%s/%s", CONF.MAIL_DIR, path);
            (void) unlink(path);
            ok = FALSE;
        }
@@ -2370,14 +2155,13 @@ do_data_queue_relay(clientenv *clenv, const char *recvline, size_t recvlen,
     /* Restore string to original value */
     *restore = '@';
 
-    mmsql_gunlock("mmmail_relayqueue");
-
     /*
      * We now want to notify mmrelayd that it should verify for ready to
      * relay mail as soon as possible instead of waiting until its next
      * scheduled round.
      */
-    do_data_queue_notify(clenv);
+    if (ok)
+       do_data_queue_notify(clenv);
 
     return ok;
 }
@@ -2476,10 +2260,6 @@ do_data_queue_notify_connect(void)
     return fd;
 }
 
-#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.
  * It comports the main loop and state switcher.
@@ -2843,87 +2623,106 @@ hosts_expire_thread_iterator(hashnode_t *hnod, void *udata)
 static void *
 db_gc_thread(void *args)
 {
+    PGconn     *pgconn;
     int                rounds;
 
+    if ((pgconn = PQconnectdb(CONF.DB_INFO)) == NULL) {
+       syslog(LOG_NOTICE, "do_gc_thread() - PQconnectdb()");
+       exit(EXIT_FAILURE);
+    }
+
     for (rounds = 1; ; rounds++) {
-       MYSQL_RES       *mysqlres;
+       PGresult        *pgres;
+       bool            ok;
 
        (void) pthread_sleep(60);
 
        /*
-        * Perform dangling mailbox directories cleanup
+        * Perform dangling mailbox directories cleanup every minute.
+        * We do this in a transaction and automatically revert if we couldn't
+        * delete an object for any reason.  On success the sequence counter
+        * is also reset.  We use the id field for sorting.
         */
-       if ((mysqlres = mmsql_query("SELECT boxdelete_address FROM boxdelete",
-                       -1)) != NULL) {
-           char                addr[64];
-           MYSQL_ROW           *row;
-           unsigned long       *lengths;
-
-           while ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres)) != NULL) {
-               lengths = mysql_fetch_lengths(mysqlres);
-               if (row[0] != NULL) {
-                   char        deladdr[64], query[1024];
-                   bool        delete;
-
-                   delete = FALSE;
-                   *deladdr = '\0';
-                   mm_memcpy(addr, row[0], lengths[0]);
-                   addr[lengths[0]] = '\0';
-
-                   /*
-                    * Important security sanity checking: Make sure that
-                    * this actually consists of a valid address. We
-                    * wouldn't want to perform anything if arbitrary
-                    * entries were filled by a malicious user, bypassing
-                    * the HTTP frontend security somehow, or the MySQL
-                    * ones. We also don't need to do anything if we are not
-                    * using files for message storage.
-                    */
-#if defined(MMMAIL_FILE)
-                   if (valid_address(NULL, deladdr, 64, addr, HOST_NORES)) {
-                       MYSQL_RES       *mysqlres2;
-
-                       /*
-                        * And make sure that no box entry exists for it!
-                        */
-                       (void) snprintf(query, 1023,
-                                       "SELECT box_address FROM box WHERE "
-                                       "box_address='%s'", deladdr);
-                       if ((mysqlres2 = mmsql_query(query, mm_strlen(query)))
-                               != NULL) {
-                           if (mysql_num_rows(mysqlres2) == 0)
-                               delete = TRUE;
-                           (void) mmsql_free_result(mysqlres2);
-                       }
+       if ((pgres = PQexec(pgconn, "BEGIN")) == NULL)
+           continue;
+       PQclear(pgres);
+
+       ok = TRUE;
+       if ((pgres = PQexec(pgconn,
+               "SELECT id,type,path FROM file_gc_queue ORDER BY id"))
+               != NULL) {
+           int                 i, t;
+           PGresult            *pgres2;
+           unsigned long long  id;
+           char                path[1024];
+
+           for (i = 0, t = PQntuples(pgres); i < t; i++) {
+               id = strtoull(PQgetvalue(pgres, i, 0), NULL, 10);
+               (void) snprintf(path, 1023, "%s/%s", CONF.MAIL_DIR,
+                               PQgetvalue(pgres, i, 2));
+               if (*(PQgetvalue(pgres, i, 1)) == 'f') {
+                   if (unlink(path) != 0) {
+                       ok = FALSE;
+                       goto skip;
+                   }
+               } else {
+                   if (rmdir(path) != 0) {
+                       ok = FALSE;
+                       goto skip;
                    }
-#endif /* defined(MMMAIL_FILE) */
-                   /* Delete db entry unconditionally */
-                   (void) snprintf(query, 1023,
-                                   "DELETE FROM boxdelete WHERE "
-                                   "boxdelete_address='%s'", addr);
-                   (void) mmsql_command(query, mm_strlen(query));
-                   /* Perform actual deletion, only if safe to */
-                   if (delete)
-                       db_gc_thread_delete(deladdr);
+               }
+skip:
+               if (!ok) {
+                   mmsyslog(0, LOGLEVEL, "Couldn't delete '%s'", path);
+                   (void) snprintf(path, 1023,
+                           "DELETE FROM file_gc_queue WHERE id=%llu", id);
+                   if ((pgres2 = PQexec(pgconn, path)) != NULL)
+                       PQclear(pgres2);
+                   ok = TRUE;
+                   continue;
                }
            }
-           (void) mmsql_free_result(mysqlres);
+           PQclear(pgres);
+       }
+       if (ok) {
+           if ((pgres = PQexec(pgconn, "DELETE FROM file_gc_queue")) != NULL)
+               PQclear(pgres);
+           else
+               ok = FALSE;
+       }
+       if (ok) {
+           if ((pgres = PQexec(pgconn,
+               "SELECT pg_catalog.setval(pg_catalog.pg_get_serial_sequence("
+               "'file_gc_queue','id'),1,true)")) != NULL)
+               PQclear(pgres);
+           else
+               ok = FALSE;
+       }
+       if (ok) {
+           if ((pgres = PQexec(pgconn, "COMMIT")) != NULL)
+               PQclear(pgres);
+       } else {
+           if ((pgres = PQexec(pgconn, "ROLLBACK")) != NULL)
+               PQclear(pgres);
        }
 
+       /*
+        * Perform database optimizations every 24 hours.
+        */
        if (rounds == 1440) {
            rounds = 0;
 
-           /*
-            * Perform database optimization every 24 hours
-            */
 #define OPTIMIZE(s) do {                                               \
-    if ((mysqlres = mmsql_query("OPTIMIZE TABLE " s, -1)) != NULL)     \
-       (void) mmsql_free_result(mysqlres);                             \
+    if ((pgres = PQexec(pgconn, "VACUUM ANALYZE " s)) != NULL)         \
+       PQclear(pgres);                                                 \
+    else                                                               \
+       mmsyslog(0, LOGLEVEL, "Couldn't optimize " s);                  \
 } while (/* CONSTCOND */0)
 
            OPTIMIZE("alias");
            OPTIMIZE("box");
            OPTIMIZE("boxdelete");
+           OPTIMIZE("file_gc_queue");
            OPTIMIZE("filter");
            OPTIMIZE("mail");
            OPTIMIZE("nofrom");
@@ -2941,34 +2740,3 @@ db_gc_thread(void *args)
     pthread_exit(NULL);
     return NULL;
 }
-
-static void
-db_gc_thread_delete(const char *addr)
-{
-    char               dirpath[256], filepath[256];
-    DIR                        *dir;
-    struct dirent      ent, *res;
-
-    /*
-     * Now <path> holds the actual directory to delete mail from. We know
-     * that there only exist first level files in it, and we must ensure to
-     * delete all of them, as well as the actual mailbox directory afterwards.
-     */
-    if ((dir = opendir(dirpath)) == NULL) {
-       syslog(LOG_NOTICE, "db_gc_thread_delete(%s) - opendir(%s) == %s",
-               addr, dirpath, strerror(errno));
-       return;
-    }
-
-    while (readdir_r(dir, &ent, &res) == 0 && res == &ent) {
-       (void) snprintf(filepath, 255, "%s/%s", dirpath, ent.d_name);
-       if (unlink(filepath) != 0)
-           syslog(LOG_NOTICE, "db_gc_thread_delete(%s) - unlink(%s) == %s",
-                   addr, filepath, strerror(errno));
-    }
-    if (rmdir(dirpath) != 0)
-       syslog(LOG_NOTICE, "db_gc_thread_delete(%s) - rmdir(%s) == %s",
-               addr, dirpath, strerror(errno));
-
-    closedir(dir);
-}
index e46674b..d9668ca 100644 (file)
@@ -1,7 +1,7 @@
-/* $Id: mmsmtpd.h,v 1.34 2007/03/13 20:28:22 mmondor Exp $ */
+/* $Id: mmsmtpd.h,v 1.35 2007/03/16 17:24:10 mmondor Exp $ */
 
 /*
- * Copyright (C) 2001-2004, Matthew Mondor
+ * Copyright (C) 2001-2007, Matthew Mondor
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
 #include <mmstat.h>
 #include <mmlimitrate.h>
 
+#include <libpq-fe.h>
+
 
 
 
 /* DEFINITIONS */
 #define DAEMON_NAME    "mmsmtpd"
-#define DAEMON_VERSION "mmmail-0.1.0/mmondor"
+#define DAEMON_VERSION "mmmail-0.2.0/mmondor"
 
 /* Negative states are used by the state swapper, others are real states */
 #define STATE_ERROR    -3
@@ -108,11 +110,11 @@ enum data_reason {
 #define ASYNC_RESQUERY 1
 
 /* Error registration macro */
-#define REGISTER_ERROR(x)      do { \
-    (x)->errors++; \
-       if (CONF.DELAY_ON_ERROR) \
-           pthread_sleep((x)->errors); \
-} while(FALSE)
+#define REGISTER_ERROR(x)      do {                                    \
+    (x)->errors++;                                                     \
+       if (CONF.DELAY_ON_ERROR)                                        \
+           pthread_sleep((x)->errors);                                 \
+} while (/* CONSTCOND */0)
 
 /* Evaluates if a character is valid for addresses and hostnames */
 #define VALID_ADDR_CHAR(c)     \
@@ -127,9 +129,8 @@ enum data_reason {
 /* We store config file read results in this structure */
 typedef struct config {
     char LOCK_PATH[256], 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], MAIL_DIR[256],
-       MMRELAYD_SOCKET_PATH[256];
+       LOG_FACILITY[32], SERVER_NAMES[1024], LISTEN_IPS[1024], DB_INFO[1024],
+       MAIL_DIR[256], MMRELAYD_SOCKET_PATH[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,
@@ -159,9 +160,10 @@ typedef struct clientenv {
     unsigned long messages;    /* Messages user sent us */
     unsigned long rcpts;       /* Number of RCPT accepted */
     struct iface *iface;       /* Current interface user connected through */
-    struct async_clenv *aclenv;        /* Thread context for async_call() */
     list_t rcpt;               /* Cached recepients to send mail to */
-    mmstat_t vstat, pstat;     /* mmstat(3) handles */
+    struct async_clenv *aclenv;        /* Thread context for async_call() */
+    mmstat_t vstat, pstat;     /* Persistent mmstat(3) handles */
+    PGconn *pgconn;            /* Persistent pgsql connection */
 } clientenv;
 
 /* Used for RCPT addresses */
@@ -207,9 +209,8 @@ typedef struct command {
 
 /* Information for a mailbox */
 struct box_info {
-    long max_size, size, max_msgs, msgs;
-    bool filter;
-    char filter_type;
+    long       max_size, size, max_msgs, msgs;
+    bool       filter, filter_type;
 };
 
 /* For fast command lookup */
@@ -276,16 +277,18 @@ static u_int32_t commandnode_keyhash(const void *, size_t);
 static int commandnode_keycmp(const void *, const void *, size_t);
 static bool reply(fdbuf *, int, bool, const char *, ...);
 
+static bool clientenv_constructor(pnode_t *);
+static void clientenv_destructor(pnode_t *);
 static clientenv *alloc_clientenv(void);
 static bool init_clientenv(clientenv *, bool);
 static clientenv *free_clientenv(clientenv *);
 static void empty_rcpts(list_t *);
-static bool check_alias(char *);
-static bool check_nofrom(const char *, const char *);
+static bool check_alias(clientenv *, char *);
+static bool check_nofrom(clientenv *);
 static int lock_check(const char *);
 static int best_match(const char *, const char *);
-static bool local_address(struct box_info *, const char *);
-static bool box_filter_allow(const char *, const char *, char);
+static bool local_address(clientenv *, struct box_info *, const char *);
+static bool box_filter_allow(clientenv *, const char *, const char *, bool);
 static void rfc_time(char *);
 static bool valid_address(clientenv *, char *, size_t, char *, int);
 static bool valid_host(clientenv *, char *, int, bool, bool);
@@ -295,11 +298,7 @@ static int validate_msg_line(char *, ssize_t *, int *, void *);
 static bool do_data(clientenv *);
 inline static size_t 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 address_relay_allow(clientenv *, int *, const char *);
 static void iso_time(char *);
 static bool message_write(char *, const char *, size_t, struct fdbrb_buffer *,
@@ -311,9 +310,6 @@ static bool do_data_queue_relay(clientenv *, const char *, size_t, struct
        fdbrb_buffer *, rcptnode *);
 static void do_data_queue_notify(clientenv *);
 static int do_data_queue_notify_connect(void);
-#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 *);
@@ -332,7 +328,6 @@ static void *hosts_expire_thread(void *);
 static bool hosts_expire_thread_iterator(hashnode_t *, void *);
 
 static void *db_gc_thread(void *);
-static void db_gc_thread_delete(const char *);
 
 
 
index af96d8a..4f0cb8a 100644 (file)
@@ -1,4 +1,4 @@
-/* $Id: mmpasswd.c,v 1.6 2004/05/06 00:05:35 mmondor Exp $ */
+/* $Id: mmpasswd.c,v 1.7 2007/03/16 17:24:11 mmondor Exp $ */
 
 /*
  * Copyright (C) 2002-2004, Matthew Mondor
@@ -52,7 +52,7 @@
 
 MMCOPYRIGHT("@(#) Copyright (c) 2002-2004\n\
 \tMatthew Mondor. All rights reserved.\n");
-MMRCSID("$Id: mmpasswd.c,v 1.6 2004/05/06 00:05:35 mmondor Exp $");
+MMRCSID("$Id: mmpasswd.c,v 1.7 2007/03/16 17:24:11 mmondor Exp $");
 
 
 
@@ -68,10 +68,7 @@ MMRCSID("$Id: mmpasswd.c,v 1.6 2004/05/06 00:05:35 mmondor Exp $");
  */
 
 struct salt_md5 {
-    /* Restrict salt to 6 characters since PHP crypt() seems to have this
-     * limit.
-     */
-    char salt[7];
+    char salt[9];
 };
 
 struct hash_md5 {