-/* $Id: mmsmtpd.c,v 1.13 2003/03/30 21:06:00 mmondor Exp $ */
+/* $Id: mmsmtpd.c,v 1.102 2009/02/04 09:02:25 mmondor Exp $ */
/*
- * Copyright (C) 2001-2003, Matthew Mondor
+ * Copyright (C) 2001-2008, Matthew Mondor
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* 4. The name of Matthew Mondor may not be used to endorse or promote
* products derived from this software without specific prior written
* permission.
+ * 5. Redistribution of source code may not be released under the terms of
+ * any GNU Public License derivate.
*
* THIS SOFTWARE IS PROVIDED BY MATTHEW MONDOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
+#include <stdbool.h>
+#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
+#include <errno.h>
+#include <string.h> /* strerror(3) */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>
#include <resolv.h>
+#include <sys/un.h>
#include <syslog.h>
-#include <pth.h>
#include <signal.h>
#include <time.h>
+#include <dirent.h>
#include <ctype.h>
+#include <pthread.h>
+
#include <mmtypes.h>
#include <mmreadcfg.h>
#include <mmfd.h>
#include <mmlist.h>
+#include <mmpool.h>
+#include <mmhash.h>
#include <mmserver.h>
-#include <mmsql.h>
#include <mmlog.h>
#include <mmstr.h>
#include <mmstring.h>
#include <mmstat.h>
+#include <mmlimitrate.h>
#include "mmsmtpd.h"
+#include "../mmrelayd/mmrelayd.h"
-MMCOPYRIGHT("@(#) Copyright (c) 2002-2003\n\
+MMCOPYRIGHT("@(#) Copyright (c) 2001-2007\n\
\tMatthew Mondor. All rights reserved.\n");
-MMRCSID("$Id: mmsmtpd.c,v 1.13 2003/03/30 21:06:00 mmondor Exp $");
+MMRCSID("$Id: mmsmtpd.c,v 1.102 2009/02/04 09:02:25 mmondor Exp $");
/* Here consists of the commands we support */
static command commands[] = {
- /* Hash, LogLevel, Cmd, Args, Description (NULL=unimplemented) */
- {0, 3, "NOOP", "NOOP", "Does nothing"},
- {0, 3, "RSET", "RSET", "Resets system to initial state"},
- {0, 3, "QUIT", "QUIT", "Disconnects, exits"},
- {0, 3, "HELP", "HELP [<topic>]", "Gives HELP information"},
- {0, 2, "HELO", "HELO <hostname>", "Permits to authenticate"},
- {0, 2, "MAIL", "MAIL FROM:<sender>", "Specifies sender of message"},
- {0, 2, "RCPT", "RCPT TO:<recipient>", "Specifies a recipient"},
- {0, 3, "DATA", "DATA", "Accepts the message ending with ."},
- {0, 4, "BEER", NULL, NULL},
- {0, 0, NULL, NULL, NULL}
+ /* LogLevel, Cmd, Args, Description (NULL=unimplemented) */
+ {3, "NOOP", "NOOP", "Does nothing"},
+ {3, "RSET", "RSET", "Resets system to initial state"},
+ {3, "QUIT", "QUIT", "Disconnects, exits"},
+ {3, "HELP", "HELP [<topic>]", "Gives HELP information"},
+ {2, "HELO", "HELO <hostname>", "Permits to authenticate"},
+ {2, "MAIL", "MAIL FROM:<sender>", "Specifies sender of message"},
+ {2, "RCPT", "RCPT TO:<recipient>", "Specifies a recipient"},
+ {3, "DATA", "DATA", "Accepts the message ending with ."},
+ {4, "BEER", NULL, NULL},
+ {0, NULL, NULL, NULL}
};
/* The system is simple enough that only one state is required, each command
};
/* The definitions of our many various states (-: */
-static struct state states[] = {
+static const struct state states[] = {
{state_all, 0, "Abnormal error"}
};
static int LOGLEVEL;
/* Used for clenv allocation buffering */
-static pool_t cpool;
-static pth_mutex_t cpool_lock;
+static pool_t clenv_pool;
+static pthread_mutex_t clenv_lock = PTHREAD_MUTEX_INITIALIZER;
-/* Used for flood protection cache */
-static pool_t mpool;
-static list_t mlist;
-static pth_mutex_t mlist_lock;
+/* Used for the flood protection cache */
+static pool_t hosts_pool;
+static hashtable_t hosts_table;
+static pthread_mutex_t hosts_lock = PTHREAD_MUTEX_INITIALIZER;
/* Used for rcpt allocation buffering */
-static pool_t rpool;
-static pth_mutex_t rpool_lock;
+static pool_t rcpt_pool;
+static pthread_mutex_t rcpt_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/* Pool used to optimize creating/destroying mmfd mutexes */
+static pthread_mutex_t mutexes_lock = PTHREAD_MUTEX_INITIALIZER;
+static pool_t mutexes_pool;
-/* Pool used to optimize creatiing/destroying mmfd mutexes */
-static pth_mutex_t mutexes_lock;
-static pool_t pmutexes;
+/* For fast command lookup */
+static pool_t command_pool;
+static hashtable_t command_table;
/* Global bandwidth shaping fdb context */
static fdbcontext fdbc;
/* Quick index to RCPT command replies (see mmsmtpd.h for matching defines) */
-static struct reply_messages rcpt_msg[] = {
+static const struct reply_messages rcpt_msg[RCPT_MAX] = {
{250, "Recipient ok"},
{503, "Use MAIL first"},
{552, "Too many recipients"},
{501, "Invalid address"},
+ {501, "Unknown address"},
+ {501, "Relaying denied"},
{250, "Recipient already added"},
{402, "Mailbox full, try again later"},
{402, "Rate exceeded, try again later"},
+ {571, "Delivery not authorized, message refused"},
{452, "Internal error, contact administrator"}
};
/* Fast index to DATA replies (see headerfile for matching keywords) */
-static struct reply_messages data_msg[] = {
+static const struct reply_messages data_msg[DATA_MAX] = {
{354, "Submit message ending with a single ."},
{250, "Ok, mail delivered"},
{552, "Too much mail data"},
+ {452, "Input timeout"},
{552, "Too many hops"},
- {452, "Internal error"}
+ {571, "Delivery not authorized, message refused"},
+ {452, "Internal error, contact administrator"}
};
/* Pth support for mmfd library (that library rocks my world :) */
static fdfuncs gfdf = {
malloc,
free,
- pth_poll,
- pth_read,
- pth_write,
- pth_sleep,
- pth_usleep,
- _pth_mutex_create,
- _pth_mutex_destroy,
- _pth_mutex_lock,
- _pth_mutex_unlock,
- _pth_thread_yield
+ poll,
+ read,
+ write,
+ pthread_sleep,
+ pthread_usleep,
+ thread_mutex_create,
+ thread_mutex_destroy,
+ thread_mutex_lock,
+ thread_mutex_unlock,
+ NULL,
+ thread_eintr
+};
+
+/*
+ * Used to speed up VALID_ADDR_CHAR()
+ */
+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,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 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,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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, 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, 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
+};
+/*
+ * And for VALID_HOST_CHAR()
+ */
+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,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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,
+ 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 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, 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, 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
};
+/*
+ * Connection to mmrelayd(8) establishment
+ */
+static int relayd_sock = -1;
+static pthread_mutex_t relayd_lock = PTHREAD_MUTEX_INITIALIZER;
+
{
uid_t uid;
gid_t *gids;
- char *conf_file = "/etc/mmsmtpd.conf";
+ char *conf_file = "/usr/local/etc/mmsmtpd.conf";
int ngids, ret = -1;
long facility;
- char *db_host;
bool strlist;
- CRES cres;
- CARG *cargp;
- CARG cargs[] = {
- {CAT_STR, 1, 255, CAS_UNTOUCHED, "CHROOT_DIR", CONF.CHROOT_DIR},
- {CAT_STR, 1, 255, CAS_UNTOUCHED, "PID_PATH", CONF.PID_PATH},
- {CAT_STR, 1, 31, CAS_UNTOUCHED, "USER", CONF.USER},
- {CAT_STR, 1, 255, CAS_UNTOUCHED, "GROUPS", CONF.GROUPS},
- {CAT_STR, 1, 31, CAS_UNTOUCHED, "LOG_FACILITY", CONF.LOG_FACILITY},
- {CAT_STR, 1, 1023, CAS_UNTOUCHED, "SERVER_NAMES",
- CONF.SERVER_NAMES},
- {CAT_STR, 1, 1023, CAS_UNTOUCHED, "LISTEN_IPS", CONF.LISTEN_IPS},
- {CAT_STR, 1, 63, CAS_UNTOUCHED, "DB_HOST", CONF.DB_HOST},
- {CAT_STR, 1, 31, CAS_UNTOUCHED, "DB_USER", CONF.DB_USER},
- {CAT_STR, 1, 31, CAS_UNTOUCHED, "DB_PASSWORD", CONF.DB_PASSWORD},
- {CAT_STR, 1, 31, CAS_UNTOUCHED, "DB_DATABASE", CONF.DB_DATABASE},
- {CAT_VAL, 1, 32, CAS_UNTOUCHED, "ASYNC_PROCESSES",
- &CONF.ASYNC_PROCESSES},
- {CAT_VAL, 1, 9999, CAS_UNTOUCHED, "ALLOC_BUFFERS",
- &CONF.ALLOC_BUFFERS},
- {CAT_VAL, 0, 4, CAS_UNTOUCHED, "LOG_LEVEL", &CONF.LOG_LEVEL},
- {CAT_VAL, 1, 65535, CAS_UNTOUCHED, "LISTEN_PORT",
- &CONF.LISTEN_PORT},
- {CAT_VAL, 1, 1000, CAS_UNTOUCHED, "MAX_ERRORS", &CONF.MAX_ERRORS},
- {CAT_VAL, 1, 99999, CAS_UNTOUCHED, "MAX_IPS", &CONF.MAX_IPS},
- {CAT_VAL, 1, 99999, CAS_UNTOUCHED, "MAX_PER_IP", &CONF.MAX_PER_IP},
- {CAT_VAL, 0, 99999, CAS_UNTOUCHED, "CONNECTION_RATE",
+ cres_t cres;
+ carg_t *cargp;
+ carg_t cargs[] = {
+ {CAT_STR, CAF_NONE, 1, 255, "LOCK_PATH", CONF.LOCK_PATH},
+ {CAT_STR, CAF_NONE, 1, 255, "CHROOT_DIR", CONF.CHROOT_DIR},
+ {CAT_STR, CAF_NONE, 1, 255, "PID_PATH", CONF.PID_PATH},
+ {CAT_STR, CAF_NONE, 1, 31, "USER", CONF.USER},
+ {CAT_STR, CAF_NONE, 1, 255, "GROUPS", CONF.GROUPS},
+ {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, 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},
+ {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},
+ {CAT_VAL, CAF_NONE, 1, 65535, "LISTEN_PORT", &CONF.LISTEN_PORT},
+ {CAT_VAL, CAF_NONE, 1, 1000, "MAX_ERRORS", &CONF.MAX_ERRORS},
+ {CAT_VAL, CAF_NONE, 1, 99999, "MAX_IPS", &CONF.MAX_IPS},
+ {CAT_VAL, CAF_NONE, 1, 99999, "MAX_PER_IP", &CONF.MAX_PER_IP},
+ {CAT_VAL, CAF_NONE, 0, 99999, "CONNECTION_RATE",
&CONF.CONNECTION_RATE},
- {CAT_VAL, 0, 99999, CAS_UNTOUCHED, "CONNECTION_PERIOD",
+ {CAT_VAL, CAF_NONE, 1, 99999, "CONNECTION_PERIOD",
&CONF.CONNECTION_PERIOD},
- {CAT_VAL, 1, 99999, CAS_UNTOUCHED, "INPUT_TIMEOUT",
- &CONF.INPUT_TIMEOUT},
- {CAT_VAL, 0, 99999, CAS_UNTOUCHED, "BANDWIDTH_IN",
- &CONF.BANDWIDTH_IN},
- {CAT_VAL, 0, 99999, CAS_UNTOUCHED, "BANDWIDTH_OUT",
- &CONF.BANDWIDTH_OUT},
- {CAT_VAL, 0, 99999, CAS_UNTOUCHED, "GBANDWIDTH_IN",
- &CONF.GBANDWIDTH_IN},
- {CAT_VAL, 0, 99999, CAS_UNTOUCHED, "GBANDWIDTH_OUT",
- &CONF.GBANDWIDTH_OUT},
- {CAT_VAL, 1, 99999, CAS_UNTOUCHED, "MAX_RCPTS", &CONF.MAX_RCPTS},
- {CAT_VAL, 1, 999999, CAS_UNTOUCHED, "MAX_DATA_LINES",
+ {CAT_VAL, CAF_NONE, 1, 99999, "INPUT_TIMEOUT", &CONF.INPUT_TIMEOUT},
+ {CAT_VAL, CAF_NONE, 0, 99999, "BANDWIDTH_IN", &CONF.BANDWIDTH_IN},
+ {CAT_VAL, CAF_NONE, 0, 99999, "BANDWIDTH_OUT", &CONF.BANDWIDTH_OUT},
+ {CAT_VAL, CAF_NONE, 0, 99999, "GBANDWIDTH_IN", &CONF.GBANDWIDTH_IN},
+ {CAT_VAL, CAF_NONE, 0, 99999, "GBANDWIDTH_OUT", &CONF.GBANDWIDTH_OUT},
+ {CAT_VAL, CAF_NONE, 1, 99999, "MAX_RCPTS", &CONF.MAX_RCPTS},
+ {CAT_VAL, CAF_NONE, 1, 999999, "MAX_DATA_LINES",
&CONF.MAX_DATA_LINES},
- {CAT_VAL, 1, 99999999, CAS_UNTOUCHED, "MAX_DATA_SIZE",
+ {CAT_VAL, CAF_NONE, 1, 99999999, "MAX_DATA_SIZE",
&CONF.MAX_DATA_SIZE},
- {CAT_VAL, 1, 999, CAS_UNTOUCHED, "MAX_HOPS", &CONF.MAX_HOPS},
- {CAT_VAL, 1, 999999, CAS_UNTOUCHED, "FLOOD_MESSAGES",
+ {CAT_VAL, CAF_NONE, 1, 999, "MAX_HOPS", &CONF.MAX_HOPS},
+ {CAT_VAL, CAF_NONE, 1, 999999, "FLOOD_MESSAGES",
&CONF.FLOOD_MESSAGES},
- {CAT_VAL, 1, 120, CAS_UNTOUCHED, "FLOOD_EXPIRES",
- &CONF.FLOOD_EXPIRES},
- {CAT_VAL, 50, 999999, CAS_UNTOUCHED, "FLOOD_CACHE",
- &CONF.FLOOD_CACHE},
- {CAT_BOOL, 0, 0, CAS_UNTOUCHED, "RESOLVE_HOSTS",
- &CONF.RESOLVE_HOSTS},
- {CAT_BOOL, 0, 0, CAS_UNTOUCHED, "RESOLVE_HELO",
- &CONF.RESOLVE_HELO},
- {CAT_BOOL, 0, 0, CAS_UNTOUCHED, "RESOLVE_MX_MAIL",
- &CONF.RESOLVE_MX_MAIL},
- {CAT_BOOL, 0, 0, CAS_UNTOUCHED, "REQUIRE_HELO",
- &CONF.REQUIRE_HELO},
- {CAT_BOOL, 0, 0, CAS_UNTOUCHED, "FLOOD_PROTECTION",
- &CONF.FLOOD_PROTECTION},
- {CAT_BOOL, 0, 0, CAS_UNTOUCHED, "STATFAIL_ADDRESS",
- &CONF.STATFAIL_ADDRESS},
- {CAT_BOOL, 0, 0, CAS_UNTOUCHED, "STATFAIL_FLOOD",
- &CONF.STATFAIL_FLOOD},
- {CAT_BOOL, 0, 0, CAS_UNTOUCHED, "STATFAIL_FULL",
- &CONF.STATFAIL_FULL},
- {CAT_BOOL, 0, 0, CAS_UNTOUCHED, "STATFAIL_TIMEOUT",
+ {CAT_VAL, CAF_NONE, 1, 120, "FLOOD_EXPIRES", &CONF.FLOOD_EXPIRES},
+ {CAT_VAL, CAF_NONE, 50, 999999, "FLOOD_CACHE", &CONF.FLOOD_CACHE},
+ {CAT_BOOL, CAF_NONE, 0, 0, "RESOLVE_HOSTS", &CONF.RESOLVE_HOSTS},
+ {CAT_BOOL, CAF_NONE, 0, 0, "RESOLVE_HELO", &CONF.RESOLVE_HELO},
+ {CAT_BOOL, CAF_NONE, 0, 0, "RESOLVE_MX_MAIL", &CONF.RESOLVE_MX_MAIL},
+ {CAT_BOOL, CAF_NONE, 0, 0, "RESOLVE_MX_RCPT", &CONF.RESOLVE_MX_RCPT},
+ {CAT_BOOL, CAF_NONE, 0, 0, "REQUIRE_HELO", &CONF.REQUIRE_HELO},
+ {CAT_BOOL, CAF_NONE, 0, 0, "REQUIRE_HOP", &CONF.REQUIRE_HOP},
+ {CAT_BOOL, CAF_NONE, 0, 0, "FLOOD_PROTECTION", &CONF.FLOOD_PROTECTION},
+ {CAT_BOOL, CAF_NONE, 0, 0, "STATFAIL_HELO", &CONF.STATFAIL_HELO},
+ {CAT_BOOL, CAF_NONE, 0, 0, "STATFAIL_NOHELO", &CONF.STATFAIL_NOHELO},
+ {CAT_BOOL, CAF_NONE, 0, 0, "STATFAIL_NOFROM", &CONF.STATFAIL_NOFROM},
+ {CAT_BOOL, CAF_NONE, 0, 0, "STATFAIL_ADDRESS", &CONF.STATFAIL_ADDRESS},
+ {CAT_BOOL, CAF_NONE, 0, 0, "STATFAIL_RELAY", &CONF.STATFAIL_RELAY},
+ {CAT_BOOL, CAF_NONE, 0, 0, "STATFAIL_FLOOD", &CONF.STATFAIL_FLOOD},
+ {CAT_BOOL, CAF_NONE, 0, 0, "STATFAIL_FULL", &CONF.STATFAIL_FULL},
+ {CAT_BOOL, CAF_NONE, 0, 0, "STATFAIL_TIMEOUT",
&CONF.STATFAIL_TIMEOUT},
- {CAT_BOOL, 0, 0, CAS_UNTOUCHED, "STATFAIL_EOF",
- &CONF.STATFAIL_EOF},
- {CAT_BOOL, 0, 0, CAS_UNTOUCHED, "DELAY_ON_ERROR",
- &CONF.DELAY_ON_ERROR},
- {CAT_END, 0, 0, 0, NULL, NULL}
+ {CAT_BOOL, CAF_NONE, 0, 0, "STATFAIL_EOF", &CONF.STATFAIL_EOF},
+ {CAT_BOOL, CAF_NONE, 0, 0, "STATFAIL_FILTER", &CONF.STATFAIL_FILTER},
+ {CAT_BOOL, CAF_NONE, 0, 0, "DELAY_ON_ERROR", &CONF.DELAY_ON_ERROR},
+ {CAT_BOOL, CAF_NONE, 0, 0, "RELAYING", &CONF.RELAYING},
+ {CAT_END, CAF_NONE, 0, 0, NULL, NULL}
};
- CMAP cmap[] = {
+ cmap_t cmap[] = {
{"LOG_AUTH", LOG_AUTH},
{"LOG_AUTHPRIV", LOG_AUTHPRIV},
{"LOG_CRON", LOG_CRON},
{async_resquery, sizeof(struct async_resquery_msg)},
{NULL, 0}
};
- struct mmsql_threadsupport mmsqlfuncs = {
- _pth_mutex_create,
- _pth_mutex_destroy,
- _pth_mutex_lock,
- _pth_mutex_unlock,
- _pth_thread_yield
- };
- MMSTAT vstat;
+ pthread_t hosts_table_thread = NULL;
+ pthread_t mmmail_db_gc_thread = NULL;
+ pthread_attr_t threadattr;
/* Set defaults */
*CONF.CHROOT_DIR = 0;
+ mm_strcpy(CONF.LOCK_PATH, "/var/run/mmsmtpd.lock");
mm_strcpy(CONF.PID_PATH, "/var/run/mmsmtpd.pid");
mm_strcpy(CONF.USER, "mmmail");
- mm_strcpy(CONF.GROUPS, "mmmail mmstat");
+ mm_strcpy(CONF.GROUPS, "mmmail,mmstat");
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;
CONF.ALLOC_BUFFERS = 1;
CONF.LOG_LEVEL = 3;
CONF.RESOLVE_HOSTS = FALSE;
CONF.RESOLVE_HELO = FALSE;
CONF.RESOLVE_MX_MAIL = FALSE;
+ CONF.RESOLVE_MX_RCPT = FALSE;
CONF.REQUIRE_HELO = FALSE;
+ CONF.REQUIRE_HOP = FALSE;
CONF.FLOOD_PROTECTION = TRUE;
+ CONF.STATFAIL_HELO = TRUE;
+ CONF.STATFAIL_NOHELO = TRUE;
+ CONF.STATFAIL_NOFROM = TRUE;
CONF.STATFAIL_ADDRESS = TRUE;
+ CONF.STATFAIL_RELAY = TRUE;
CONF.STATFAIL_FLOOD = TRUE;
CONF.STATFAIL_FULL = TRUE;
CONF.STATFAIL_TIMEOUT = TRUE;
CONF.STATFAIL_EOF = TRUE;
+ CONF.STATFAIL_FILTER = TRUE;
CONF.DELAY_ON_ERROR = FALSE;
+ CONF.RELAYING = FALSE;
/* Advertize */
printf("\r\n+++ %s (%s)\r\n\r\n", DAEMON_NAME, DAEMON_VERSION);
/* Read config file */
if (argc == 2)
conf_file = argv[1];
- if (!mmreadcfg(conf_file, cargs, &cres)) {
+ if (!mmreadcfg(&cres, cargs, conf_file)) {
/* Error parsing configuration file, report which */
printf("\nError parsing '%s'\n", conf_file);
printf("Error : %s\n", mmreadcfg_strerr(cres.CR_Err));
if (*(cres.CR_Data)) printf("Data : %s\n", cres.CR_Data);
- if (cres.CR_Number != -1) {
- cargp = &cargs[cres.CR_Number];
- printf("Keyword: %s\n", cargp->CA_KW);
+ if ((cargp = cres.CR_Keyword) != NULL) {
+ printf("Keyword: %s\n", cargp->CA_Keyword);
printf("Minimum: %ld\n", cargp->CA_Min);
printf("Maximum: %ld\n", cargp->CA_Max);
}
+ if (cres.CR_Line != -1)
+ printf("Line : %d\n", cres.CR_Line);
printf("\n");
exit(-1);
}
+ /* Ensure that only a single instance with same configuration runs */
+ if (lock_check(CONF.LOCK_PATH) == -1) {
+ printf("\nCouldn't lock file '%s' (already running?)\n\n",
+ CONF.LOCK_PATH);
+ exit(-1);
+ }
+
/* Post parsing */
if (!mmmapstring(cmap, CONF.LOG_FACILITY, &facility)) {
printf("\nUnknown syslog facility %s\n\n", CONF.LOG_FACILITY);
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 {
+ struct stat st;
+
+ if (stat(CONF.MAIL_DIR, &st) == -1) {
+ printf("\nMAIL_DIR could not be found: '%s'\n\n", CONF.MAIL_DIR);
+ exit(-1);
+ }
+ if (!S_ISDIR(st.st_mode)) {
+ printf("\nMAIL_DIR not a directory: '%s'\n\n", CONF.MAIL_DIR);
+ exit(-1);
+ }
+ }
/* Finally init everything */
openlog(DAEMON_NAME, LOG_PID | LOG_NDELAY, facility);
/* In case we chroot(2), the following is a good idea to execute first */
mmstat_initialize();
- mmstat_init(&vstat, TRUE, TRUE);
- mmstat_transact(&vstat, TRUE);
- mmstat(&vstat, STAT_DELETE, 0, "mmsmtpd.current.connections");
- 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);
+ {
+ mmstat_t vstat;
+
+ mmstat_init(&vstat, TRUE, TRUE);
+ mmstat_transact(&vstat, TRUE);
+ mmstat(&vstat, STAT_DELETE, 0, "mmsmtpd|current|connections");
+ mmstat(&vstat, STAT_DELETE, 0, "mmsmtpd|who|*");
+ mmstat_transact(&vstat, FALSE);
}
+ res_init();
make_daemon(CONF.PID_PATH, CONF.CHROOT_DIR);
async_init(afuncs, CONF.ASYNC_PROCESSES, uid, gids, ngids);
/* Things which shouldn't be part of the async pool processes */
- packcommands(commands, 4);
- pth_init();
- async_init_pth();
- pth_mutex_init(&cpool_lock);
- pth_mutex_init(&mlist_lock);
- pth_mutex_init(&rpool_lock);
- pth_mutex_init(&mutexes_lock);
+ async_init_pthread();
/* Allocate necessary pools */
/* Client nodes */
- pool_init(&cpool, malloc, free, sizeof(clientenv),
- (16384 * 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(&rpool, malloc, free, sizeof(rcptnode),
+ pool_init(&rcpt_pool, "rcpt_pool", malloc, free, NULL, NULL,
+ sizeof(rcptnode),
(16384 * CONF.ALLOC_BUFFERS) / sizeof(rcptnode), 0, 0);
/* Mutexes pool for mmfd */
- pool_init(&pmutexes, malloc, free, sizeof(struct mutexnode),
+ pool_init(&mutexes_pool, "mutexes_pool", malloc, free, NULL, NULL,
+ sizeof(struct mutexnode),
(16384 * CONF.ALLOC_BUFFERS) / sizeof(struct mutexnode), 0, 0);
+
+ pthread_attr_init(&threadattr);
+ pthread_attr_setdetachstate(&threadattr, PTHREAD_CREATE_JOINABLE);
+
/* Rate nodes */
if (CONF.FLOOD_PROTECTION) {
- pool_init(&mpool, malloc, free, sizeof(mnode), CONF.FLOOD_CACHE, 1, 1);
- LIST_INIT(&mlist);
+ pool_init(&hosts_pool, "hosts_pool", malloc, free, NULL, NULL,
+ sizeof(hostnode), CONF.FLOOD_CACHE, 1, 1);
+ hashtable_init(&hosts_table, "hosts_table", CONF.FLOOD_CACHE, 1,
+ malloc, free, mm_memcmp, mm_memhash32, FALSE);
+ pthread_create(&hosts_table_thread, &threadattr, hosts_expire_thread,
+ NULL);
}
+ /* 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);
- if (POOL_VALID(&cpool) && POOL_VALID(&rpool) && POOL_VALID(&pmutexes) &&
- strlist && (!CONF.FLOOD_PROTECTION || POOL_VALID(&mpool))) {
+ if (hash_commands(commands, 4) && POOL_VALID(&clenv_pool) &&
+ POOL_VALID(&rcpt_pool) && POOL_VALID(&mutexes_pool) && strlist &&
+ (!CONF.FLOOD_PROTECTION || (POOL_VALID(&hosts_pool) &&
+ HASHTABLE_VALID(&hosts_table) &&
+ hosts_table_thread != NULL))) {
+ 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,
CONF.MAX_IPS, CONF.MAX_PER_IP, CONF.CONNECTION_RATE,
CONF.CONNECTION_PERIOD, CONF.INPUT_TIMEOUT,
- CONF.LISTEN_PORT, CONF.RESOLVE_HOSTS, handleclient);
+ CONF.LISTEN_PORT, CONF.RESOLVE_HOSTS, handleclient,
+ utdata_constructor, utdata_destructor);
mmfreegidarray(gids);
ret = 0;
- mmsql_close();
- mmsql_exit();
} else {
printf("\nOut of memory\n\n");
syslog(LOG_NOTICE, "* Out of memory");
}
- if (strlist) mmstrexit();
- if (POOL_VALID(&pmutexes)) pool_destroy(&pmutexes);
- if (POOL_VALID(&mpool)) pool_destroy(&mpool);
- if (POOL_VALID(&rpool)) pool_destroy(&rpool);
- if (POOL_VALID(&cpool)) pool_destroy(&cpool);
+ if (strlist)
+ mmstrexit();
+ if (hosts_table_thread != NULL) {
+ pthread_kill(hosts_table_thread, SIGTERM);
+ pthread_join(hosts_table_thread, NULL);
+ }
+ if (mmmail_db_gc_thread != NULL) {
+ pthread_kill(mmmail_db_gc_thread, SIGTERM);
+ pthread_join(mmmail_db_gc_thread, NULL);
+ }
+ if (HASHTABLE_VALID(&command_table))
+ hashtable_destroy(&command_table, FALSE);
+ if (POOL_VALID(&command_pool))
+ pool_destroy(&command_pool);
+ if (HASHTABLE_VALID(&hosts_table))
+ hashtable_destroy(&hosts_table, FALSE);
+ if (POOL_VALID(&mutexes_pool))
+ pool_destroy(&mutexes_pool);
+ if (POOL_VALID(&hosts_pool))
+ pool_destroy(&hosts_pool);
+ if (POOL_VALID(&rcpt_pool))
+ pool_destroy(&rcpt_pool);
+ if (POOL_VALID(&clenv_pool))
+ pool_destroy(&clenv_pool);
fdbcdestroy(&fdbc);
kill(0, SIGTERM);
/* First check if a topic was specified */
if ((col = mm_straspl(args, cmdline, 2)) == 2) {
- register int i = 0;
- register int32_t chash;
- register bool valid;
+ uint32_t chash;
+ struct commandnode *nod;
/* Help requested on a topic */
- valid = FALSE;
- if ((chash = mm_strpack32(args[1], 4)) != -1) {
- for (i = 0; commands[i].name; i++) {
- if (commands[i].hash == chash) {
- valid = TRUE;
- break;
- }
- }
- }
-
+ nod = NULL;
+ if ((chash = mm_strpack32(args[1], 4)) != 0)
+ nod = (struct commandnode *)hashtable_lookup(&command_table,
+ &chash, sizeof(uint32_t));
col = 0;
- if (valid) {
- reply(fdb, 214, TRUE, commands[i].args);
- reply(fdb, 214, TRUE, " %s", commands[i].desc);
+ if (nod != NULL) {
+ reply(fdb, 214, TRUE, nod->command->args);
+ reply(fdb, 214, TRUE, " %s", nod->command->desc);
col = 2;
}
- if (col)
+ if (col > 0)
reply(fdb, 214, FALSE, "End of HELP information");
else {
reply(fdb, 504, FALSE, "Unknown HELP topic");
reply(fdb, 214, TRUE, "Available topics:");
fdbwrite(fdb, "214-", 4);
col = 1;
- for (i = 0; (tmp = commands[i].name); i++) {
- if (commands[i].desc) {
- if (col == 0) fdbwrite(fdb, "\r\n214-", 6);
+ for (i = 0; (tmp = commands[i].name) != NULL; i++) {
+ if (commands[i].desc != NULL) {
+ if (col == 0)
+ fdbwrite(fdb, "\r\n214-", 6);
col++;
- if (col > 4) col = 0;
+ if (col > 4)
+ col = 0;
fdbprintf(fdb, " %s", tmp);
}
}
char *args[3], *cmdline = clenv->buffer;
if ((mm_straspl(args, cmdline, 2)) == 2) {
- if (!clenv->helo) {
+ if (clenv->helo == NULL) {
if (valid_host(clenv, args[1],
- CONF.RESOLVE_HELO ? HOST_RES : HOST_NORES, TRUE)) {
- if ((clenv->helo = mmstrdup(args[1])) == NULL)
- mmsyslog(0, LOGLEVEL, "all_helo() - * Out of memory!");
+ CONF.RESOLVE_HELO ? HOST_RES : HOST_NORES, TRUE,
+ TRUE) &&
+ ((clenv->helo = mmstrdup(args[1])) != NULL)) {
reply(fdb, 250, FALSE, "%s ok", clenv->iface->hostname);
} else {
+ if (CONF.STATFAIL_HELO)
+ mmstat(&clenv->pstat, STAT_UPDATE, 1,
+ "mmsmtpd|failed|helo|%s",
+ clenv->c_ipaddr);
reply(fdb, 501, FALSE, "Invalid hostname");
REGISTER_ERROR(clenv);
}
{
int nextstate = STATE_CURRENT;
fdbuf *fdb = clenv->fdb;
- char addr[64];
+ char addr[128];
bool valid;
+ bool nofrom = false, checkednofrom = false;
+
+ /* Allow nofrom list to avoid HELO even if required */
+ if (CONF.REQUIRE_HELO) {
+ if (clenv->helo == NULL) {
+ nofrom = check_nofrom(clenv);
+ checkednofrom = true;
+ if (!nofrom) {
+ if (CONF.STATFAIL_NOHELO)
+ mmstat(&clenv->pstat, STAT_UPDATE, 1,
+ "mmsmtpd|failed|nohelo|%s",
+ clenv->c_ipaddr);
+ reply(fdb, 503, FALSE, "Use HELO first");
+ REGISTER_ERROR(clenv);
+ return STATE_CURRENT;
+ }
+ }
+ }
- if (!CONF.REQUIRE_HELO || clenv->helo) {
-
- if (!clenv->from) {
+ if (clenv->from == NULL) {
- valid = FALSE;
- if (!(mm_strncasecmp(" FROM:<>", &clenv->buffer[4], 8))) {
- /* Some systems use empty MAIL FROM like this, make sure
- * that IP address or hostname is allowed to do this.
- */
- valid = check_nofrom(clenv->c_ipaddr, clenv->c_hostname);
- if (valid) *addr = 0;
- } else
- valid = valid_address(clenv, addr, clenv->buffer,
- (CONF.RESOLVE_MX_MAIL) ? HOST_RES_MX : HOST_NORES);
-
- if (valid) {
- if ((clenv->from = (char *)mmstrdup(addr)))
- reply(fdb, 250, FALSE, "Sender ok");
- else
- nextstate = STATE_ERROR;
- } else {
- reply(fdb, 501, FALSE, "Invalid address");
- REGISTER_ERROR(clenv);
- }
+ valid = false;
+ if ((mm_strncasecmp(" FROM:<>", &clenv->buffer[4], 8)) == 0) {
+ /* Some systems use empty MAIL FROM like this, make sure
+ * that IP address or hostname is allowed to do this.
+ * If so, we also want to make sure not to perform any type
+ * of envelope based filtering for this post.
+ */
+ if (!checkednofrom)
+ nofrom = check_nofrom(clenv);
+ if ((valid = nofrom))
+ *addr = '\0';
+ clenv->nofrom = true;
+ if (!valid && CONF.STATFAIL_NOFROM)
+ mmstat(&clenv->pstat, STAT_UPDATE, 1,
+ "mmsmtpd|failed|nofrom|%s",
+ clenv->c_ipaddr);
+ } else {
+ valid = valid_address(clenv, addr, 128, clenv->buffer,
+ (CONF.RESOLVE_MX_MAIL) ? HOST_RES_MX : HOST_NORES);
+ clenv->nofrom = false;
+ }
+ if (valid) {
+ if ((clenv->from = mmstrdup(addr)) != NULL)
+ reply(fdb, 250, FALSE, "Sender ok");
+ else
+ nextstate = STATE_ERROR;
} else {
- reply(fdb, 503, FALSE, "Sender already specified");
+ reply(fdb, 501, FALSE, "Invalid address");
REGISTER_ERROR(clenv);
}
} else {
- reply(fdb, 503, FALSE, "Use HELO first");
+ reply(fdb, 503, FALSE, "Sender already specified");
REGISTER_ERROR(clenv);
}
- return (nextstate);
+ return nextstate;
}
fdbuf *fdb = clenv->fdb;
char addr[64], foraddr[64], *line = clenv->buffer;
int reason;
- bool valid;
- long max_size, size, max_msgs, msgs;
- u_int64_t ahash;
+ struct box_info boxinfo;
+ uint64_t ahash;
+ bool valid, relay;
/* I have opted for an elimination process here as there are many cases
* which can cause an RCPT to be refused, and alot of indenting was to
* be avoided for clarity. Functions could also be used but it has not
* been necessary this far, and we want the code performance to be optimal.
*/
- valid = TRUE;
- reason = RCPT_OK;
+ relay = FALSE;
- if (!clenv->from) {
- valid = FALSE;
+ if (clenv->from == NULL) {
reason = RCPT_NOFROM;
+ goto end;
}
/* First make sure to not allow more RCPTs than CONF.MAX_RCPTS */
- if (valid) {
- if (!(clenv->rcpt.nodes < CONF.MAX_RCPTS)) {
- valid = FALSE;
- reason = RCPT_MAX;
- if (CONF.STATFAIL_FLOOD)
- mmstat(&clenv->pstat, STAT_UPDATE, 1,
- "mmsmtpd.failed.flood.%s", clenv->c_ipaddr);
- }
+ if (!(DLIST_NODES(&clenv->rcpt) < CONF.MAX_RCPTS)) {
+ reason = RCPT_MANY;
+ if (CONF.STATFAIL_FLOOD)
+ mmstat(&clenv->pstat, STAT_UPDATE, 1,
+ "mmsmtpd|failed|flood|rcpt|%s|%s",
+ clenv->c_ipaddr, clenv->from);
+ goto end;
+ }
+
+ /* Only continue if address seems valid */
+ if (!valid_address(clenv, addr, 64, line, HOST_NORES)) {
+ reason = RCPT_INVALID;
+ goto end;
}
/* Verify if existing address, if it isn't verify for any alias
* matching it and of course check for address validity again for
* safety. This way we make sure that an alias pattern does not over-
* ride an existing address, and that we only archive a message into
- * an existing mailbox.
+ * an existing mailbox. <addr> is not modified if there exist no alias for
+ * the address. Otherwise, <foraddr> keeps the original address.
*/
- if (valid) {
- valid = FALSE;
- if (valid_address(clenv, addr, line, HOST_NORES)) {
- mm_strcpy(foraddr, addr);
- valid = local_address(addr, &max_size, &size, &max_msgs, &msgs);
- if (!valid) {
- if (check_alias(addr))
- valid = local_address(addr, &max_size, &size, &max_msgs,
- &msgs);
+ valid = FALSE;
+ (void) mm_strcpy(foraddr, 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 (CONF.RELAYING && !valid) {
+ /* Address is not local. If relaying is allowed, we must be
+ * able to verify that the address indeed belongs to a
+ * non-local domain, and if so, verify that the sender is
+ * allowed to relay messages. If it belongs to a local domain,
+ * we must treat it as invalid local address, however.
+ */
+ if ((valid = address_relay_allow(clenv, &reason, foraddr))) {
+ if (CONF.RESOLVE_MX_RCPT) {
+ /* We know that the address is in a valid format, but we are
+ * now required to verify that the hostname has an MX record.
+ */
+ char *domain;
+
+ for (domain = foraddr; *domain != '@'; domain++) ;
+ domain++;
+ if (!valid_host(clenv, domain, HOST_RES_MX, FALSE, FALSE)) {
+ reason = RCPT_INVALID;
+ goto end;
+ }
}
+ relay = TRUE;
}
- if (!valid) {
- reason = RCPT_INVALID;
- if (CONF.STATFAIL_ADDRESS)
+ }
+ if (!valid) {
+ switch (reason) {
+ case RCPT_RELAY:
+ if (CONF.STATFAIL_RELAY) {
mmstat(&clenv->pstat, STAT_UPDATE, 1,
- "mmsmtpd.failed.address.%s", clenv->c_ipaddr);
+ "mmsmtpd|failed|relay|%s|%s|%s",
+ clenv->c_ipaddr, clenv->from, foraddr);
+ }
+ break;
+ case RCPT_UNKNOWN:
+ if (CONF.STATFAIL_ADDRESS) {
+ mmstat(&clenv->pstat, STAT_UPDATE, 1,
+ "mmsmtpd|failed|address|%s|%s|%s",
+ clenv->c_ipaddr, clenv->from, addr);
+ }
+ break;
}
+ goto end;
}
- /* Make sure mailbox quota limits are respected */
- if (valid) {
- if (!((size <= max_size) && (msgs <= max_msgs))) {
+ /* These only apply to local addresses */
+ if (!relay) {
+ /*
+ * Ensure to observe allow filters if any set for box, except for mail
+ * with an empty FROM address from allowed servers
+ */
+ if (boxinfo.filter && !clenv->nofrom) {
+ if (!box_filter_allow(clenv, addr, clenv->from,
+ boxinfo.filter_type)) {
+ reason = RCPT_FILTER;
+ if (CONF.STATFAIL_FILTER)
+ mmstat(&clenv->pstat, STAT_UPDATE, 1,
+ "mmsmtpd|failed|filter|%s|%s|%s",
+ clenv->c_ipaddr, clenv->from, addr);
+ goto end;
+ }
+ }
+ /* Make sure mailbox quota limits are respected */
+ if (!((boxinfo.size < boxinfo.max_size) &&
+ (boxinfo.msgs < boxinfo.max_msgs))) {
mmsyslog(0, LOGLEVEL, "%s mailbox full (%ld,%ld %ld,%ld)",
- addr, max_size, size, max_msgs, msgs);
- valid = FALSE;
+ addr, boxinfo.max_size, boxinfo.size, boxinfo.max_msgs,
+ boxinfo.msgs);
reason = RCPT_FULL;
if (CONF.STATFAIL_FULL)
mmstat(&clenv->pstat, STAT_UPDATE, 1,
- "mmsmtpd.failed.full.%s", addr);
+ "mmsmtpd|failed|full|%s", addr);
+ goto end;
}
}
/* 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
*/
- if (valid) {
+ {
register rcptnode *rnode;
- register int cnt;
ahash = mm_strhash64(addr);
- cnt = 0;
- for (rnode = (rcptnode *)clenv->rcpt.top; rnode;
- rnode = (rcptnode *)rnode->node.node.next) {
+ DLIST_FOREACH(&clenv->rcpt, rnode) {
if (rnode->hash == ahash) {
- valid = FALSE;
reason = RCPT_EXISTS;
- break;
- }
- cnt++;
- if (cnt > 64) {
- cnt = 0;
- pth_yield(NULL);
+ goto end;
}
}
}
- /* If CONF.FLOOD_PROTECTION is on, make sure that we respect the rate
- * of CONF.FLOOD_MESSAGES within CONF.FLOOD_EXPIRES for this client
+ /* If CONF.FLOOD_PROTECTION is TRUE, make sure that we respect the rate
+ * of CONF.FLOOD_MESSAGES within CONF.FLOOD_EXPIRES for this client.
*/
- if (valid && CONF.FLOOD_PROTECTION) {
- register mnode *mnod, *next;
- register int cnt;
- register u_int64_t hash;
- register time_t t;
-
- cnt = 0;
- if (clenv->c_hostname) hash = mm_strhash64(clenv->c_hostname);
- else hash = mm_strhash64(clenv->c_ipaddr);
- t = time(NULL);
-
- pth_mutex_acquire(&mlist_lock, FALSE, NULL);
- /* First acquire our mnode, or create it if required */
- mnod = (mnode *)mlist.top;
- while (mnod) {
- next = (mnode *)mnod->node.node.next;
- if (mnod->expires < t) {
- /* This entry has expired, expunge it */
- LIST_UNLINK(&mlist, (node_t *)mnod);
- pool_free((pnode_t *)mnod);
- } else if (mnod->hash == hash) break;
- /* Of course as we still are holding the mutex another
- * thread in the same state would wait still...
- */
- cnt++;
- if (cnt > 64) {
- cnt = 0;
- pth_yield(NULL);
- }
- mnod = next;
- }
+ if (CONF.FLOOD_PROTECTION) {
+ register hostnode *hnod;
+ register size_t len;
+ register char *entry;
- if (!mnod) {
- /* Create a new mnode since none matched */
- if ((mnod = (mnode *)pool_alloc(&mpool, FALSE)) != NULL) {
- mnod->hash = hash;
- mnod->expires = t + (CONF.FLOOD_EXPIRES * 60);
- mnod->posts = 1;
- LIST_APPEND(&mlist, (node_t *)mnod);
+ if (clenv->c_hostname != NULL)
+ entry = clenv->c_hostname;
+ else
+ entry = clenv->c_ipaddr;
+ len = mm_strlen(entry);
+
+ valid = TRUE;
+ pthread_mutex_lock(&hosts_lock);
+ /* First acquire our hostnode, or create it if required */
+ if ((hnod = (hostnode *)hashtable_lookup(&hosts_table, entry, len + 1))
+ == NULL) {
+ /* Create a new entry */
+ if ((hnod = (hostnode *)pool_alloc(&hosts_pool, FALSE)) != NULL) {
+ mm_memcpy(hnod->host, entry, len + 1);
+ LR_INIT(&hnod->lr, CONF.FLOOD_MESSAGES,
+ CONF.FLOOD_EXPIRES * 60, time(NULL));
+ hashtable_link(&hosts_table, (hashnode_t *)hnod, entry,
+ len + 1, FALSE);
} else {
valid = FALSE;
reason = RCPT_FLOOD;
- mmsyslog(0, LOGLEVEL, "* FLOOD_CACHE not large enough");
+ mmsyslog(0, LOGLEVEL, "FLOOD_CACHE not large enough");
}
- } else {
- /* We found a cached entry for this client,
- * check limits and update.
- */
- mnod->posts++;
- if (mnod->posts > CONF.FLOOD_MESSAGES) {
+ }
+ if (valid) {
+ /* Check and update limits */
+ if (!lr_allow(&hnod->lr, 1, 0, FALSE)) {
valid = FALSE;
reason = RCPT_FLOOD;
mmsyslog(0, LOGLEVEL,
- "%08X Considered flood and rejected (%ld message(s) \
-within last %ld minute(s))", clenv->id, mnod->posts,
- CONF.FLOOD_EXPIRES);
+ "%08lX Considered flood and rejected (%ld message(s) "
+ "within last %ld minute(s))",
+ clenv->id, LR_POSTS(&hnod->lr), CONF.FLOOD_EXPIRES);
}
}
- pth_mutex_release(&mlist_lock);
+ pthread_mutex_unlock(&hosts_lock);
- if (!valid && CONF.STATFAIL_FLOOD)
- mmstat(&clenv->pstat, STAT_UPDATE, 1,
- "mmsmtpd.failed.flood.%s", clenv->c_ipaddr);
+ if (!valid) {
+ if (CONF.STATFAIL_FLOOD)
+ mmstat(&clenv->pstat, STAT_UPDATE, 1,
+ "mmsmtpd|failed|flood|message|%s|%s|%s",
+ clenv->c_ipaddr, clenv->from, addr);
+ goto end;
+ }
}
- /* Finally append new RCPT to list */
- if (valid) {
+ /* Finally append new RCPT node to list */
+ {
register rcptnode *rnode;
reason = RCPT_ERROR;
- pth_mutex_acquire(&rpool_lock, FALSE, NULL);
- rnode = (rcptnode *)pool_alloc(&rpool, FALSE);
- pth_mutex_release(&rpool_lock);
- if (rnode) {
- mm_strcpy(rnode->address, addr);
+ pthread_mutex_lock(&rcpt_lock);
+ rnode = (rcptnode *)pool_alloc(&rcpt_pool, FALSE);
+ pthread_mutex_unlock(&rcpt_lock);
+ if (rnode != NULL) {
+ mm_strcpy(rnode->address, (relay ? foraddr : addr));
mm_strcpy(rnode->foraddress, foraddr);
rnode->hash = ahash;
- LIST_APPEND(&clenv->rcpt, (node_t *)rnode);
+ rnode->relay = relay;
+ DLIST_APPEND(&clenv->rcpt, (node_t *)rnode);
reason = RCPT_OK;
clenv->rcpts++;
} else {
- mmsyslog(0, LOGLEVEL,
- "%08X * all_rcpt() - Error allocating new rcpt node",
- clenv->id);
+ DEBUG_PRINTF("all_rcpt", "pool_alloc(rcpt_pool)");
nextstate = STATE_ERROR;
}
}
+end:
+
/* Reply with appropriate message */
if (reason != RCPT_OK)
REGISTER_ERROR(clenv);
int nextstate = STATE_CURRENT;
fdbuf *fdb = clenv->fdb;
- if (clenv->buffer[4] == 0) {
- if (clenv->from) {
- if (clenv->rcpt.nodes) {
+ if (clenv->buffer[4] == '\0') {
+ if (clenv->from != NULL) {
+ if (DLIST_NODES(&clenv->rcpt) > 0) {
if (!do_data(clenv))
nextstate = STATE_ERROR;
else
-/* Used to initialize command table hashes */
-static void
-packcommands(struct command *cmd, size_t min)
-{
- while (cmd->name) {
- cmd->hash = mm_strpack32(cmd->name, min);
- cmd++;
+/* Used to initialize command hash table */
+static bool
+hash_commands(struct command *cmd, size_t min)
+{
+ int i;
+
+ /* We do not care for any unfreed resources, the program will free them
+ * and exit if we return FALSE.
+ */
+ if (!pool_init(&command_pool, "command_pool", malloc, free, NULL, NULL,
+ sizeof(struct commandnode), 64, 1, 0) ||
+ !hashtable_init(&command_table, "command_table", 64, 1, malloc,
+ free, commandnode_keycmp, commandnode_keyhash, TRUE))
+ return FALSE;
+
+ for (i = 0; cmd->name != NULL; cmd++, i++) {
+ struct commandnode *nod;
+
+ if ((nod = (struct commandnode *)pool_alloc(&command_pool, FALSE))
+ == NULL)
+ return FALSE;
+ if ((nod->hash = mm_strpack32(cmd->name, min)) == 0)
+ return FALSE;
+ nod->command = cmd;
+ nod->index = i;
+ if (!hashtable_link(&command_table, (hashnode_t *)nod, &nod->hash,
+ sizeof(uint32_t), TRUE)) {
+ DEBUG_PRINTF("hash_commands", "hashtable_link(%s)", cmd->name);
+ return FALSE;
+ }
}
+
+ return TRUE;
+}
+
+
+/* A quick hashtable_hash() replacement which already deals with unique
+ * 32-bit values
+ */
+/* ARGSUSED */
+static uint32_t
+commandnode_keyhash(const void *data, size_t len)
+{
+ return *((uint32_t *)data);
+}
+
+
+/* A quick memcmp() replacement which only needs to compare two 32-bit values
+ */
+/* ARGSUSED */
+static int
+commandnode_keycmp(const void *src, const void *dst, size_t len)
+{
+ return *((uint32_t *)src) - *((uint32_t *)dst);
}
vsnprintf(buf, 1023, fmt, arg_ptr);
va_end(arg_ptr);
- if (cont) err = fdbprintf(fdb, "%d-%s\r\n", code, buf);
- else err = fdbprintf(fdb, "%d %s\r\n", code, buf);
+ if (cont)
+ err = fdbprintf(fdb, "%d-%s\r\n", code, buf);
+ else
+ err = fdbprintf(fdb, "%d %s\r\n", code, buf);
mmsyslog(3, LOGLEVEL, "> %d (%s)", code, buf);
}
+static bool
+clientenv_constructor(pnode_t *pn)
+{
+ clientenv *clenv = (clientenv *)pn;
+
+ mmstat_init(&clenv->vstat, TRUE, TRUE);
+ mmstat_init(&clenv->pstat, TRUE, FALSE);
+ DLIST_INIT(&clenv->rcpt);
+ clenv->helo = clenv->from = NULL;
+
+ return TRUE;
+}
+
+/* ARGSUSED */
+static void
+clientenv_destructor(pnode_t *pn)
+{
+ /*
+ clientenv *clenv = (clientenv *)pn;
+ */
+
+ /* NOOP */
+}
+
+
+static void *
+utdata_constructor(void)
+{
+ PGconn *pgconn;
+
+ if ((pgconn = PQconnectdb(CONF.DB_INFO)) == NULL)
+ syslog(LOG_NOTICE, "PQconnectdb()");
+
+ return pgconn;
+}
+
+static void
+utdata_destructor(void *utdata)
+{
+
+ if (utdata != NULL)
+ PQfinish(utdata);
+}
+
+
/* Allocate and prepare a clenv. Returns NULL on error */
static clientenv *
alloc_clientenv(void)
{
clientenv *clenv;
- pth_mutex_acquire(&cpool_lock, FALSE, NULL);
- clenv = (clientenv *)pool_alloc(&cpool, TRUE);
- pth_mutex_release(&cpool_lock);
-
- if (clenv) {
- mmstat_init(&clenv->vstat, TRUE, TRUE);
- mmstat_init(&clenv->pstat, TRUE, FALSE);
- }
+ pthread_mutex_lock(&clenv_lock);
+ clenv = (clientenv *)pool_alloc(&clenv_pool, FALSE);
+ pthread_mutex_unlock(&clenv_lock);
return (clenv);
}
static bool
init_clientenv(clientenv *clenv, bool helo)
{
- if (helo && clenv->helo) clenv->helo = mmstrfree(clenv->helo);
- if (clenv->from) clenv->from = mmstrfree(clenv->from);
+ if (helo && clenv->helo != NULL)
+ clenv->helo = mmstrfree(clenv->helo);
+ if (clenv->from != NULL)
+ clenv->from = mmstrfree(clenv->from);
empty_rcpts(&clenv->rcpt);
return (TRUE);
static clientenv *
free_clientenv(clientenv *clenv)
{
- if (clenv->helo) mmstrfree(clenv->helo);
- if (clenv->from) mmstrfree(clenv->from);
+ if (clenv->helo != NULL)
+ clenv->helo = mmstrfree(clenv->helo);
+ if (clenv->from != NULL)
+ clenv->from = mmstrfree(clenv->from);
empty_rcpts(&clenv->rcpt);
- pth_mutex_acquire(&cpool_lock, FALSE, NULL);
+ pthread_mutex_lock(&clenv_lock);
pool_free((pnode_t *)clenv);
- pth_mutex_release(&cpool_lock);
+ pthread_mutex_unlock(&clenv_lock);
return (NULL);
}
static void
empty_rcpts(list_t *lst)
{
- node_t *nod;
+ node_t *nod, *tmp;
- pth_mutex_acquire(&rpool_lock, FALSE, NULL);
- while ((nod = lst->top)) {
- LIST_UNLINK(lst, nod);
+ pthread_mutex_lock(&rcpt_lock);
+ for (nod = DLIST_TOP(lst); nod != NULL; nod = tmp) {
+ tmp = DLIST_NEXT(nod);
pool_free((pnode_t *)nod);
}
- pth_mutex_release(&rpool_lock);
+ pthread_mutex_unlock(&rcpt_lock);
+
+ DLIST_INIT(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 *args[3], oaddr[64], query[1024];
-
- if ((mm_strspl(args, addr, 2, '@')) == 2) {
- MYSQL_RES *mysqlres;
-
- mm_strncpy(oaddr, args[0], 63);
- snprintf(query, 1024, "SELECT alias_pattern,alias_box FROM alias \
-WHERE alias_domain='%s'", args[1]);
- if ((mysqlres = mmsql_query(query, mm_strlen(query))) != NULL) {
- if ((mysql_num_rows(mysqlres)) > 0) {
- char pat[64];
- int cur = 0, max = -1, cnt = 0;
- 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] && row[1]) {
- mm_memcpy(pat, row[0], lengths[0]);
- pat[lengths[0]] = 0;
- if ((cur = best_match(oaddr, pat)) != -1) {
- if (cur > max) {
- /* Matches better, remember this one */
- max = cur;
- mm_memcpy(addr, row[1], lengths[1]);
- addr[lengths[1]] = 0;
- }
- }
- }
- cnt++;
- if (cnt > 64) {
- cnt = 0;
- pth_yield(NULL);
- }
- }
- if (max > -1)
- res = TRUE;
- else
- /* Restore old address we have destroyed */
- args[1][-1] = '@';
+ 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
+ * user name when performing betch-match operation. Then split the backup
+ * into user and domain.
+ */
+ (void) mm_strcpy(oaddr, addr);
+ for (user = oaddr, domain = oaddr; *domain != '@'; domain++) ;
+ *domain++ = '\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);
+ }
}
- mysqlres = mmsql_free_result(mysqlres);
}
-
+ if (max > -1) {
+ (void) mm_strcpy(addr, a);
+ res = TRUE;
+ }
+ PQclear(pgres);
}
- return (res);
+ return res;
}
* 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;
-
- if (addr == NULL && host == NULL) return (FALSE);
-
- if ((mysqlres = mmsql_query("SELECT * FROM nofrom", 20)) != NULL) {
- if ((mysql_num_rows(mysqlres)) > 0) {
- int cnt = 0;
- MYSQL_ROW *row;
- unsigned long *lengths;
-
- while ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres)) != NULL) {
- lengths = mysql_fetch_lengths(mysqlres);
- if (row[0]) {
- char pat[64];
-
- mm_memcpy(pat, row[0], lengths[0]);
- pat[lengths[0]] = 0;
- if (addr) {
- if ((best_match(addr, pat)) != -1) {
- res = TRUE;
- break;
- }
- }
- if (host) {
- if ((best_match(host, pat)) != -1) {
- res = TRUE;
- break;
- }
- }
+ bool res = false;
+ PGresult *pgres;
+
+ if (clenv->c_ipaddr == NULL && clenv->c_hostname == NULL)
+ return (false);
+
+ if ((pgres = PQexec(clenv->pgconn, "SELECT pattern FROM nofrom"))
+ != 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;
}
- cnt++;
- if (cnt > 64) {
- cnt = 0;
- pth_yield(NULL);
+ }
+ 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;
+}
+
+
+/*
+ * Used to ensure that only a single server instance with the same
+ * configuration runs at the same time.
+ */
+static int
+lock_check(const char *path)
+{
+ int fd;
+
+ if ((fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600)) != -1) {
+ if (flock(fd, LOCK_EX | LOCK_NB) == 0)
+ return 0;
+ (void) close(fd);
+ }
+
+ return -1;
}
int lit = 0;
for (; *pat != '*'; pat++, str++) {
- if (!(*str)) {
- if (*pat) return (-1);
- else return (lit);
+ if (*str == '\0') {
+ if (*pat != '\0')
+ return -1;
+ else
+ return lit;
}
- if (*str == *pat) lit++;
- else if(*pat != '?') return (-1);
+ if (tolower((int)*str) == tolower((int)*pat))
+ lit++;
+ else
+ if(*pat != '?')
+ return -1;
}
- while (pat[1] == '*') pat++;
+ while (pat[1] == '*')
+ pat++;
do {
register int tmp;
- if ((tmp = best_match(str, pat + 1)) != -1) return (lit + tmp);
- } while (*str++);
+ if ((tmp = best_match(str, pat + 1)) != -1)
+ return (lit + tmp);
+ } while (*str++ != '\0');
- return (-1);
+ return -1;
}
/* 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(const char *address, long *maxsize, long *size, long *maxmsgs,
- long *msgs)
+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 */
- snprintf(line, 1000,
- "SELECT box_max_size,box_size,box_max_msgs,box_msgs 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)) == 4) {
- lengths = mysql_fetch_lengths(mysqlres);
- if (row[0] && row[1] && row[2] && row[3]) {
- mm_memcpy(line, row[0], lengths[0]);
- line[lengths[0]] = 0;
- *maxsize = atol(line);
- mm_memcpy(line, row[1], lengths[1]);
- line[lengths[1]] = 0;
- *size = atol(line);
- mm_memcpy(line, row[2], lengths[2]);
- line[lengths[2]] = 0;
- *maxmsgs = atol(line);
- mm_memcpy(line, row[3], lengths[3]);
- line[lengths[3]] = 0;
- *msgs = atol(line);
- res = TRUE;
- } else
- syslog(LOG_NOTICE, "* local_address() - row[x]");
- } else
- syslog(LOG_NOTICE,
- "* local_address() - mysql_num_fields()");
+ bool res = FALSE;
+ 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;
}
+ PQclear(pgres);
+ }
- mysqlres = mmsql_free_result(mysqlres);
- } else
- syslog(LOG_NOTICE, "* local_address() - mmsql_query()");
+ return res;
+}
- return (res);
+
+/* Verifies if mailbox <toaddr> filters allow <fromaddr> to post */
+static bool
+box_filter_allow(clientenv *clenv, const char *toaddr, const char *fromaddr,
+ bool filter_type)
+{
+ bool res;
+ PGresult *pgres;
+ const char *params[2];
+
+ /* Default return code depends on filter type */
+ 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;
+ }
+ }
+ PQclear(pgres);
+ }
+
+ return res;
}
-/* Fills str which should be at least 32 bytes in length with current time */
+/* Fills str which should be at least 38 bytes in length with current time */
static void
rfc_time(char *str)
{
- /* Thu, 07 Dec 2000 07:36:15 -0000 */
+ /* Thu, 27 Dec 2008 05:54:12 +0000 (UTC) */
const static char *days[] = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
time_t secs;
struct tm *gtim;
- /* Calculate expiration time of the cookie */
secs = time(NULL);
gtim = gmtime(&secs);
- snprintf(str, 32, "%s, %02d %s %04d %02d:%02d:%02d -0000",
- days[gtim->tm_wday], gtim->tm_mday, months[gtim->tm_mon],
- gtim->tm_year + 1900, gtim->tm_hour, gtim->tm_min,
- gtim->tm_sec);
+ snprintf(str, 38, "%s, %02d %s %04d %02d:%02d:%02d +0000 (UTC)",
+ days[gtim->tm_wday], gtim->tm_mday, months[gtim->tm_mon],
+ gtim->tm_year + 1900, gtim->tm_hour, gtim->tm_min,
+ gtim->tm_sec);
}
/* Returns whether or not supplied address is valid, and if it is return the
- * parsed address in the supplied string. String should be at least 64 bytes.
+ * parsed address in the supplied string. String should be at least <len>
+ * bytes. <clenv> can only be NULL if HOST_NORES is used for <res>.
*/
static bool
-valid_address(clientenv *clenv, char *to, char *addr, int res)
+valid_address(clientenv *clenv, char *to, size_t len, char *addr, int res)
{
char *ptr, *a, *h;
mm_strlower(addr);
/* First locate required @ */
- for (ptr = addr; *ptr && *ptr != '@'; ptr++) ;
- if (!*ptr) return (FALSE);
+ for (ptr = addr; *ptr != '\0' && *ptr != '@'; ptr++) ;
+ if (*ptr == '\0')
+ return (FALSE);
h = ptr + 1;
/* Then scan to the left */
- for (ptr--; ptr >= addr && VALID_CHAR(*ptr); ptr--) ;
- if (h - ptr < 3 || ptr < addr) return (FALSE);
+ for (ptr--; ptr >= addr && VALID_ADDR_CHAR(*ptr); ptr--) ;
+ if (h - ptr < 3 || ptr < addr)
+ return (FALSE);
a = ++ptr;
/* Now to the right */
- for (ptr = h; *ptr && VALID_CHAR(*ptr); ptr++) ;
- if (ptr - h < 2) return (FALSE);
+ for (ptr = h; *ptr != '\0' && VALID_ADDR_CHAR(*ptr); ptr++) ;
+ if (ptr - h < 2)
+ return (FALSE);
*ptr = '\0';
/* Now validate hostname part */
- if (valid_host(clenv, h, res, FALSE)) {
- mm_strncpy(to, a, 63);
+ if (valid_host(clenv, h, res, FALSE, TRUE)) {
+ mm_strncpy(to, a, len - 1);
return (TRUE);
}
static bool
-valid_host(clientenv *clenv, char *host, int res, bool addr)
+valid_host(clientenv *clenv, char *host, int res, bool addr, bool sanity)
{
- register char *ptr;
- if (addr && res != HOST_RES_MX && valid_ipaddress(host)) return (TRUE);
+ if (addr && res != HOST_RES_MX && valid_ipaddress(clenv, host))
+ return TRUE;
- mm_strlower(host);
- /* First make sure all characters are valid */
- for (ptr = host; *ptr; ptr++)
- if (!VALID_CHAR(*ptr)) return (FALSE);
+ if (sanity) {
+ register char *ptr;
- /* Now verify that all parts of the hostname are starting with
- * an alphanumeric char
- */
- ptr = host;
- while (*ptr) {
- if (!isalnum(*ptr)) return (FALSE);
- /* Find next host part */
- while (*ptr && *ptr != '.') ptr++;
- if (*ptr == '.') {
+ mm_strlower(host);
+
+ /* First make sure all characters are valid */
+ for (ptr = host; *ptr != '\0'; ptr++)
+ if (!VALID_HOST_CHAR(*ptr))
+ return FALSE;
+
+ /* Now verify that all parts of the hostname are starting with
+ * an alphanumeric char
+ */
+ ptr = host;
+ while (*ptr != '\0') {
+ if (!isalnum((int)*ptr))
+ return FALSE;
+ /* Find next host part */
+ while (*ptr != '\0' && *ptr != '.') ptr++;
+ if (*ptr == '.') {
+ ptr++;
+ continue;
+ }
+ if (*ptr == '\0') break;
ptr++;
- continue;
}
- if (!*ptr) break;
- ptr++;
}
/* Hostname seems valid, last sanity checking test consists of optional
* resolving
*/
if (res != HOST_NORES) {
- char answer[64];
+ char answer[128];
if (res == HOST_RES_MX) {
/* Check for an MX DNS IP address entry for it */
if ((a_res_query(clenv, host, C_IN, T_MX, answer,
sizeof(answer) - 1)) == -1)
- return (FALSE);
+ return FALSE;
} else if (res == HOST_RES) {
- /* Check if hostname resolves to normal A record */
- if ((a_res_query(clenv, host, C_IN, T_A, answer,
- sizeof(answer) - 1)) == -1)
- return (FALSE);
+ /* Check if hostname resolves to normal A/AAAA record */
+ if (a_res_query(clenv, host, C_IN, T_A, answer,
+ sizeof(answer) - 1) == -1 &&
+ a_res_query(clenv, host, C_IN, T_AAAA, answer,
+ sizeof(answer) - 1) == -1)
+ return FALSE;
}
}
- return (TRUE);
+ return TRUE;
}
-/* Some more parsing magic for IP address sanity checking */
+/* IP address sanity checking */
static bool
-valid_ipaddress(const char *addr)
+valid_ipaddress(clientenv *clenv, const char *addr)
{
- char unit[5], *uptr, *utptr;
- int units;
-
- for (units = 0, uptr = unit, utptr = unit + 4; uptr < utptr; addr++) {
- if (!*addr || *addr == '.') {
- if (uptr > unit && units < 4) {
- register int n;
-
- *uptr = '\0';
- n = atoi(unit);
- if (n < 0 || n > 255) break;
- uptr = unit;
- units++;
- } else return (FALSE);
- if (!*addr) break;
- } else if (isdigit(*addr)) *uptr++ = *addr;
- else return (FALSE);
- }
- if (!(units == 4 && *addr == '\0')) return (FALSE);
+ struct server_sockaddr saddr;
- return (TRUE);
+ if (inet_pton(*(SERVER_SOCKADDR_FAMILY(&clenv->iface->address)), addr,
+ SERVER_SOCKADDR(&saddr)) == 1)
+ return TRUE;
+
+ return FALSE;
}
+/* This function is called for every line read from the message */
static int
validate_msg_line(char *line, ssize_t *len, int *res, void *udata)
{
- register struct validate_udata *ud = udata;
+ register struct validate_udata *ud = udata;
+ int eres = FDBRB_OK;
+
+ /* Verify for message termination indicator, a single '.', which is both
+ * valid in headers or body. If we're still in headers, we ensure to
+ * create missing headers and append the end of headers empty line, to
+ * avoid broken messages.
+ */
+ if (*len == 1 && *line == '.') {
+ if (ud->header) {
+ *line = '\0';
+ *len = 0;
+ eres = FDBRB_ADDSTOP;
+ goto endheader;
+ } else
+ return FDBRB_STOP;
+ }
+
+ if (ud->header) {
+
+ /* Still reading header and expecting ones */
+ char header[64], *data, *ptr, *hptr, *ehptr = &header[63];
- /* Count hops */
- if (ud->hops != -1) {
- if (!(mm_strncmp(line, "Received:", 9))) {
+ /* Empty line means that body will follow */
+ if (*len == 0)
+ goto endheader;
+
+ /* Ensure that entry seems a valid message header or also stop.
+ * Basically, make sure that a header only comports alphanumeric
+ * characters and dashes in the name, and that the name follows by
+ * ': '. Also allow continueing header lines which begin with
+ * spaces/tabs.
+ */
+ data = line;
+ *header = '\0';
+ if (*line != '\t' && *line != ' ') {
+ for (ptr = line, hptr = header;
+ *ptr != '\0' && (isalnum((int)*ptr) || *ptr == '-') &&
+ hptr < ehptr;
+ *hptr++ = *ptr++) ;
+ if (*ptr++ != ':')
+ goto endheader;
+ if (*ptr++ != ' ')
+ goto endheader;
+
+ *hptr = '\0';
+ while (*ptr != '\0' && isspace((int)*ptr))
+ ptr++;
+ data = ptr;
+ mm_strupper(header);
+ }
+
+ /* XXX
+ * Permit admin-supplied table to filter unwanted headers here.
+ */
+ if (mm_strcmp(header, "X-MAILER") == 0) {
+ if (best_match(data, "*the*bat*") != -1) {
+ *res = CFDBRB_HEADER;
+ return FDBRB_STOP;
+ }
+ }
+
+ /* Count number of Received: headers (SMTP hops) */
+ if (mm_strcmp(header, "RECEIVED") == 0) {
ud->hops++;
if (ud->hops > CONF.MAX_HOPS) {
- /* Exceeded maximum allowed number of "Received:" lines */
+ /* Exceeded allowed number of hops, cancel reception */
*res = CFDBRB_HOPS;
- return (FDBRB_STOP);
- } else ud->nhops = 0;
- } else {
- ud->nhops++;
- if (ud->nhops > 5)
- ud->hops = -1;
+ return FDBRB_STOP;
+ }
+ return FDBRB_OK;
}
- }
- /* Process .* lines */
- if (*len) {
- if (*line == '.') {
- /* Only '.' on line, stop reading */
- if (*len == 1) return (FDBRB_STOP);
- /* Strip starting . from line */
- mm_memmov(line, line + 1, *len);
+ /* Now verify for existance of headers we consider mandatory.
+ * We'll create them if necessary.
+ */
+ if (mm_strcmp(header, "MESSAGE-ID") == 0 && !ud->msgid) {
+ if ((ud->h_id = mmstrdup(data)) != NULL)
+ ud->msgid = true;
+
+ } else if (mm_strcmp(header, "DATE") == 0 && !ud->date)
+ ud->date = true;
+
+ else if (mm_strcmp(header, "FROM") == 0 && !ud->from) {
+ if ((ud->h_from = mmstrdup(data)) != NULL)
+ ud->from = true;
+
+ } else if (mm_strcmp(header, "TO") == 0 && !ud->to) {
+ if ((ud->h_to = mmstrdup(data)) != NULL)
+ ud->to = true;
+
+ } else if (mm_strcmp(header, "SUBJECT") == 0 && !ud->subject) {
+ if ((ud->h_subject = mmstrdup(data)) != NULL)
+ ud->subject = true;
+
+ } else if (mm_strcmp(header, "IN-REPLY-TO") == 0 && !ud->inreply) {
+ if ((ud->h_reply = mmstrdup(data)) != NULL)
+ ud->inreply = true;
+ }
+
+ return FDBRB_OK;
+
+ } else {
+
+ /* Reading message body */
+
+ /* ".." lines must be converted to "." ones */
+ if (*len == 2 && line[0] == '.' && line[1] == '.') {
+ line[1] = '\0';
(*len)--;
}
+
+ return FDBRB_OK;
+
}
- return (FDBRB_OK);
+endheader:
+
+ /* We reached end of headers */
+ ud->header = FALSE;
+
+ /* Drop if we require at least one hop but got none */
+ if (CONF.REQUIRE_HOP && ud->hops == 0) {
+ *res = CFDBRB_NOHOP;
+ return FDBRB_STOP;
+ }
+
+ {
+ char tline[1024], tdata[64], *cptr, *tptr;
+
+ /* Create the headers we consider mendatory if they were not supplied.
+ * We append them after all headers that were supplied, this way the
+ * Received: lines are guaranteed to be first. Note that this is only
+ * safe if the total expansion we cause does not exceed 1024 bytes,
+ * which buffer is guarenteed to have been reserved for a message line
+ * by mmfd(3)'s fdbreadbuf(). Our additionnal expansion will never
+ * exceed 320 bytes in this case.
+ */
+ cptr = tline;
+ *cptr = '\0';
+ if (!ud->msgid) {
+ tptr = cptr;
+ iso_time(tdata);
+ cptr += snprintf(cptr, 1023, "Message-Id: <%s.%08lX%02lX@%s>\r\n",
+ tdata, ud->clenv->id, ud->clenv->messages,
+ ud->clenv->iface->hostname);
+ ud->h_id = mmstrdup(&tptr[12]);
+ ud->h_id[cptr - tptr - 14] = '\0';
+ }
+ if (!ud->date) {
+ rfc_time(tdata);
+ cptr += snprintf(cptr, 1023 - (cptr - tline), "Date: %s\r\n",
+ tdata);
+ }
+ if (!ud->from) {
+ tptr = cptr;
+ cptr[1024 - (cptr - tline)] = '\0';
+ cptr += snprintf(cptr, 1023 - (cptr - tline), "From: %s\r\n",
+ ud->clenv->from);
+ ud->h_from = mmstrdup(&tptr[6]);
+ ud->h_from[cptr - tptr - 8] = '\0';
+ }
+ if (!ud->to)
+ cptr += snprintf(cptr, 1023 - (cptr - tline),
+ "To: undisclosed-recipients:;\r\n");
+
+ if (*len == 0) {
+ /* Valid end of header, an empty line. If no headers to add, all
+ * is good. Otherwise, we must simply replace the current line by
+ * the headers plus an empty line. Because the headers already
+ * contain a newline, we just copy it over the current line,
+ * fdbreadbuf() will append an additional one automatically.
+ */
+ if (cptr > tline) {
+ *cptr++ = '\0';
+ *len = cptr - tline;
+ mm_memcpy(line, tline, *len);
+ (*len)--;
+ }
+ } else {
+ /* Invalid end of header, we must insert our headers, if any,
+ * before the current line, along with an empty line.
+ * Unfortunately, this could discard some bytes at the end of the
+ * invalid last header line (the first body line) if it was too
+ * long. However, this was a malformed message anyways and needed
+ * major fixing. We could have errored instead if we were strict.
+ */
+ *len = snprintf(line, 1023, "\r\n%s\r\n", line);
+ }
+ }
+
+ return eres;
}
/* This function is called by STATE_DATA and permits the client to send
* the message data, respecting expected limits. Returns FALSE if the state
- * should switch to STATE_ERROR, on fatal error (eg: out of memory)
+ * should switch to STATE_ERROR, on fatal error (i.e. out of memory)
*/
static bool
do_data(clientenv *clenv)
{
- char line[512], line2[2048], smtptime[32], *tmp, *query;
struct fdbrb_buffer *fdbrb;
int res, err = DATA_INTERNAL;
bool ok = FALSE;
- rcptnode *rnode;
struct validate_udata ud;
reply(clenv->fdb, data_msg[DATA_SUBMIT].code, FALSE,
* CONF.MAX_DATA_SIZE bytes or CONF.MAX_DATA_LINES lines.
* See mmfd(3) man page for details, and mmlib/mmfd.c
*/
- ud.hops = ud.nhops = 0;
+ ud.hops = 0;
+ ud.msgid = ud.date = ud.from = ud.to = ud.subject = ud.inreply = FALSE;
+ ud.header = TRUE;
+ ud.clenv = clenv;
+ ud.h_from = ud.h_to = ud.h_subject = ud.h_id = ud.h_reply = NULL;
res = fdbreadbuf(&fdbrb, clenv->fdb, 32768, 1024, CONF.MAX_DATA_SIZE,
CONF.MAX_DATA_LINES, validate_msg_line, &ud, FALSE);
+
/* Map results to DATA suitable ones */
switch (res) {
case FDBRB_MEM:
- mmsyslog(0, LOGLEVEL, "%08X * Out of memory", clenv->id);
+ mmsyslog(0, LOGLEVEL, "%08lX * Out of memory", clenv->id);
err = DATA_INTERNAL;
REGISTER_ERROR(clenv);
break;
case FDBRB_OVERFLOW:
- mmsyslog(0, LOGLEVEL, "%08X * Message size too large", clenv->id);
+ mmsyslog(0, LOGLEVEL, "%08lX * Message size too large", clenv->id);
err = DATA_OVERFLOW;
REGISTER_ERROR(clenv);
break;
case FDBRB_TIMEOUT:
- mmsyslog(0, LOGLEVEL, "%08X * Input timeout", clenv->id);
+ mmsyslog(0, LOGLEVEL, "%08lX * Input timeout", clenv->id);
if (CONF.STATFAIL_TIMEOUT)
- mmstat(&clenv->pstat, STAT_UPDATE, 1, "mmsmtpd.failed.timeout.%s",
+ mmstat(&clenv->pstat, STAT_UPDATE, 1, "mmsmtpd|failed|timeout|%s",
clenv->c_ipaddr);
+ err = DATA_TIMEOUT;
break;
case FDBRB_EOF:
- mmsyslog(0, LOGLEVEL, "%08X * Unexpected EOF", clenv->id);
+ mmsyslog(0, LOGLEVEL, "%08lX * Unexpected EOF", clenv->id);
break;
case CFDBRB_HOPS:
- mmsyslog(0, LOGLEVEL, "%08X * Too many hops", clenv->id);
+ mmsyslog(0, LOGLEVEL, "%08lX * Too many hops", clenv->id);
err = DATA_HOPS;
REGISTER_ERROR(clenv);
break;
+ case CFDBRB_HEADER:
+ mmsyslog(0, LOGLEVEL, "%08lX * Forbidden header match", clenv->id);
+ err = DATA_HEADER;
+ REGISTER_ERROR(clenv);
+ break;
+ case CFDBRB_NOHOP:
+ mmsyslog(0, LOGLEVEL, "%08lX * No hop", clenv->id);
+ err = DATA_HEADER;
+ REGISTER_ERROR(clenv);
+ break;
case FDBRB_OK:
ok = TRUE;
break;
}
- if (ok) {
- /* Allocate query buffer for mysql_real_query(), should be large
- * enough to handle the worst of cases where each character would
- * be escaped to two chars, and must also hold the rest of the
- * query string. We first process the message data through
- * mysql_escape_string(), leaving enough room for the query and our
- * "Received:" line, which will be copied before the message buffer
- * for each RCPT. The message buffer will start at offset 2048
- * to make sure that there is enough room to insert the
- * RCPT-specific data (query+received).
- */
- if ((query = malloc((fdbrb->current * 2) + 2053))) {
- 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);
- fdbfreebuf(&fdbrb); /* Free immediately */
-
- /* For each RCPT, create query and execute it */
- for (rnode = (rcptnode *)clenv->rcpt.top; rnode;
- rnode = (rcptnode *)rnode->node.node.next) {
- /* Use the common message buffer, but append the query and
- * message line before it (in it's 2048 bytes free area)
- */
- snprintf(line, 511, "Received: from %s ([%s] HELO=%s)\r\n\t\
-by %s (%s) with SMTP\r\n\tid %08lX-%lu for <%s>;\r\n\t%s\r\n",
- clenv->c_hostname ? clenv->c_hostname : "(unresolved)",
- clenv->c_ipaddr,
- clenv->helo ? clenv->helo : "(unidentified)",
- clenv->iface->hostname, DAEMON_VERSION, clenv->id,
- clenv->messages, rnode->foraddress, smtptime);
- tlen = mm_strlen(line) + clen;
- snprintf(line2, 511, "INSERT INTO mail (mail_box,\
-mail_created,mail_size,mail_data) VALUES('%s',NOW(),%ld,'", rnode->address,
- (long)tlen);
- tmp = line2 + mm_strlen(line2);
- tmp += mysql_escape_string(tmp, line, mm_strlen(line));
- len = tmp - line2;
- tmp = &query[2048 - len];
- mm_memcpy(tmp, line2, len);
-
- /* Query buffer prepared, execute query. This glock is
- * required for safety between the two queries which have
- * to be performed within a single transaction. See
- * mmlib/mmsql.c for implementation details; Currently uses
- * MySQL GET_LOCK() and RELEASE_LOCK().
- */
- mmsql_glock("mmmail_boxmail");
- if (!mmsql_command(tmp, qlen + len)) {
- mmsyslog(0, LOGLEVEL,
- "%08X * Error adding message into mailbox",
- clenv->id);
- ok = FALSE;
+ if (ok)
+ ok = do_data_file(clenv, fdbrb, &ud);
+
+ if (ud.h_from != NULL)
+ mmstrfree(ud.h_from);
+ if (ud.h_to != NULL)
+ mmstrfree(ud.h_to);
+ if (ud.h_subject != NULL)
+ mmstrfree(ud.h_subject);
+ if (ud.h_id != NULL)
+ mmstrfree(ud.h_id);
+ if (ud.h_reply != NULL)
+ mmstrfree(ud.h_reply);
+
+ fdbfreebuf(&fdbrb); /* Internally only frees if not already freed */
+ if (ok)
+ err = DATA_OK;
+ reply(clenv->fdb, data_msg[err].code, FALSE, data_msg[err].msg);
+
+ /* Reset mail state (and free RCPTs) */
+ init_clientenv(clenv, FALSE);
+
+ return (ok);
+}
+
+/* Create a Received: line, isolated to prevent code duplication among
+ * different storage methods. Returns length of received line in bytes.
+ */
+inline static size_t
+do_data_received(char *line, size_t len, clientenv *clenv, rcptnode *rnode,
+ const char *smtptime)
+{
+ (void) snprintf(line, len - 1,
+ "Received: from %s (%s [%s%s])\r\n"
+ "\tby %s (%s) with SMTP id %08lX%02lX\r\n"
+ "\tfor <%s>; %s\r\n",
+ (clenv->helo != NULL ? clenv->helo : "(unidentified)"),
+ (clenv->c_hostname != NULL ? clenv->c_hostname : "(unresolved)"),
+ (mm_strchr(clenv->c_ipaddr, ':') != NULL ? "IPv6:" : ""),
+ clenv->c_ipaddr,
+ clenv->iface->hostname, DAEMON_VERSION,
+ clenv->id, clenv->messages,
+ rnode->foraddress, smtptime);
+
+ return mm_strlen(line);
+}
+
+
+/* Record statistics using mmstat(3) facility, called by do_data to prevent
+ * code duplication among different storage methods.
+ */
+static void
+do_data_stats(clientenv *clenv, rcptnode *rnode, size_t len)
+{
+ char *domptr;
+
+ mmstat_transact(&clenv->pstat, TRUE);
+
+ /* Record per-box statistics. Note that when aliases are used, the actual
+ * target mailbox is used.
+ */
+ mmstat(&clenv->pstat, STAT_UPDATE, 1, "mmmail|box|%s|messages-in",
+ rnode->address);
+ mmstat(&clenv->pstat, STAT_UPDATE, len, "mmmail|box|%s|bytes-in",
+ rnode->address);
+
+ /* And per-domain ones. The address was previously validated successfully
+ * and the '@' character is guarenteed to be present for mm_strchr().
+ */
+ domptr = mm_strchr(rnode->address, '@');
+ domptr++;
+ mmstat(&clenv->pstat, STAT_UPDATE, 1, "mmmail|domain|%s|messages-in",
+ domptr);
+ mmstat(&clenv->pstat, STAT_UPDATE, len, "mmmail|domain|%s|bytes-in",
+ domptr);
+
+ mmstat_transact(&clenv->pstat, FALSE);
+}
+
+
+/* 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
+ * unexisting) or RCPT_RELAY (relay denied for sender address/hostname).
+ * If TRUE is returned, the post can be relayed, since it does not belong to
+ * any local domains we are handling and that the client has relaying rights.
+ */
+bool
+address_relay_allow(clientenv *clenv, int *reason, const char *addr)
+{
+ bool res = TRUE;
+ const char *domain;
+ PGresult *pgres;
+
+ /* Is address to a local domain but unknown to us? */
+
+ /* We know that the supplied address is valid, it thus must have '@'.
+ * Set domain pointer to start of domain name.
+ */
+ for (domain = addr; *domain != '@'; domain++) ;
+ domain++;
+
+ /* Query database entries and search for any matching pattern. */
+ 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;
+
+ /* No, so it appears that it would need relaying. Is the client then
+ * allowed to relay messages through us? Verify via the client's IP
+ * address and/or hostname.
+ */
+ res = FALSE;
+
+ 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 {
- snprintf(line, 1000, "UPDATE box SET box_size=box_size+\
-%ld,box_msgs=box_msgs+1,box_in=NOW() WHERE box_address='%s'",
- (long)tlen, rnode->address);
- if (!mmsql_command(line, mm_strlen(line))) {
- mmsyslog(0, LOGLEVEL,
- "%08X * Error updating mailbox counters",
- clenv->id);
- ok = FALSE;
- }
}
- mmsql_gunlock("mmmail_boxmail");
- if (!ok) break;
}
+ if (clenv->c_hostname != NULL) {
+ if (best_match(clenv->c_hostname, pat) != -1) {
+ res = TRUE;
+ break;
+ }
+ }
+ }
+ PQclear(pgres);
+ }
- free(query);
- } else {
- mmsyslog(0, LOGLEVEL, "%08X * Out of memory", clenv->id);
- REGISTER_ERROR(clenv);
+ if (!res)
+ *reason = RCPT_RELAY;
+
+ return res;
+}
+
+/* Produces time in the format yyyymmddhhmmss. Supplied string must at least
+ * be 15 bytes (16 recommended).
+ */
+static void
+iso_time(char *str)
+{
+ time_t secs;
+ struct tm *gtim;
+
+ secs = time(NULL);
+ gtim = gmtime(&secs);
+
+ (void) snprintf(str, 16, "%04d%02d%02d%02d%02d%02d",
+ gtim->tm_year + 1900, gtim->tm_mon + 1, gtim->tm_mday,
+ gtim->tm_hour, gtim->tm_min, gtim->tm_sec);
+}
+
+/* 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 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
+ * must be at least 256 bytes.
+ */
+static bool
+message_write(char *path, const char *recvline, size_t recvlen,
+ struct fdbrb_buffer *fdbrb, const char *box)
+{
+ bool ok = FALSE;
+ char filetime[16];
+ int i, fd;
+ uint32_t r;
+
+ fd = -1;
+
+ /* Make sure that directory exists, performing an mkdir(2) which will
+ * fail if it already does.
+ */
+ (void) snprintf(path, 255, "%s/%s", CONF.MAIL_DIR, box);
+ if (mkdir(path, 00750) == -1 && errno != EEXIST) {
+ mmsyslog(0, LOGLEVEL, "mkdir(%s) == %s", path, strerror(errno));
+ return FALSE;
+ }
+
+ /* Generate unique filename to store the message within the mail
+ * directory. We will make 64 retries maximum in an attempt to ensure
+ * creation of a unique filename.
+ */
+ iso_time(filetime);
+ for (i = 0; i < 64; i++) {
+ r = (uint32_t)random();
+ (void) snprintf(path, 255, "%s/%s/%s.%08X", CONF.MAIL_DIR,
+ 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
+ * fdatasync(2) or fsync(2) which would considerably impact
+ * performance.
+ */
+ if (fd != -1) {
+ if (write(fd, recvline, recvlen) == recvlen &&
+ write(fd, fdbrb->array, fdbrb->current) == fdbrb->current
+ && close(fd) == 0)
+ ok = TRUE;
+ else {
+ mmsyslog(0, LOGLEVEL, "write()/close()|(%s) == %s",
+ path, strerror(errno));
+ (void) close(fd);
+ (void) 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;
+}
+
+/* For each RCPT, queue the message appropriately */
+static bool
+do_data_file(clientenv *clenv, struct fdbrb_buffer *fdbrb,
+ struct validate_udata *ud)
+{
+ char smtptime[64], recvline[1024];
+ rcptnode *rnode;
+ size_t recvlen;
+ bool ok;
+
+ ok = TRUE;
+ rfc_time(smtptime);
+
+ DLIST_FOREACH(&clenv->rcpt, rnode) {
+ /* Create Received: line */
+ recvlen = do_data_received(recvline, 1024, clenv, rnode, smtptime);
+ /* Queue for relaying or into the mailbox if local */
+ if (!rnode->relay)
+ ok = do_data_queue_box(clenv, recvline, recvlen, fdbrb, rnode, ud);
+ else {
+ if (!CONF.RELAYING)
+ ok = FALSE;
+ else
+ ok = do_data_queue_relay(clenv, recvline, recvlen, fdbrb,
+ rnode);
+ }
+ if (!ok)
+ break;
+ }
+
+ return ok;
+}
+
+/* Queue a message to a local mailbox */
+static bool
+do_data_queue_box(clientenv *clenv, const char *recvline, size_t recvlen,
+ struct fdbrb_buffer *fdbrb, rcptnode *rnode, struct validate_udata *ud)
+{
+ char path[256], v[16];
+ PGresult *pgres;
+ const char *params[9];
+
+ /* 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.
+ */
+
+ if (!message_write(path, recvline, recvlen, fdbrb, rnode->address))
+ return FALSE;
+
+ (void) snprintf(v, 15, "%ld", (long)fdbrb->current + recvlen);
+ params[0] = rnode->address;
+ params[1] = v;
+ params[2] = path;
+ params[3] = ud->h_from;
+ params[4] = ud->h_to;
+ params[5] = ud->h_subject;
+ params[6] = ud->h_id;
+ params[7] = ud->h_reply;
+ params[8] = NULL;
+ if ((pgres = PQexecParams(clenv->pgconn,
+ "INSERT INTO mail "
+ "(box,size,file,h_from,h_to,h_subject,h_id,h_reply) "
+ "VALUES($1,$2,$3,$4,$5,$6,$7,$8)",
+ 8, NULL, params, NULL, NULL, 0)) != NULL)
+ PQclear(pgres);
+ else {
+ syslog(LOG_NOTICE, "do_date_queue_box() - PQexecParams()");
+ (void) unlink(path);
+
+ return FALSE;
+ }
+
+ do_data_stats(clenv, rnode, fdbrb->current + recvlen);
+
+ return TRUE;
+}
+
+/* Queue a message for relaying */
+static bool
+do_data_queue_relay(clientenv *clenv, const char *recvline, size_t recvlen,
+ struct fdbrb_buffer *fdbrb, rcptnode *rnode)
+{
+ 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.
+ */
+
+ /* We know that the address is valid in the rcpt node, separate it into
+ * user and domain strings.
+ */
+ for (restore = rnode->address; *restore != '@'; restore++) ;
+ *restore = '\0';
+ user = rnode->address;
+ domain = &restore[1];
+
+ if (message_write(path, recvline, recvlen, fdbrb, "relayqueue")) {
+ /* Message file saved successfully, add corresponding DB entry */
+ (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;
}
+ } else
+ ok = FALSE;
+
+ /* Restore string to original value */
+ *restore = '@';
+
+ /*
+ * 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.
+ */
+ if (ok)
+ do_data_queue_notify(clenv);
+
+ return ok;
+}
+
+/*
+ * Attempt to notify mmrelayd(8) that at least one message is ready in the
+ * queue to route.
+ * XXX Broken! Do not use RELAYING = TRUE yet!
+ */
+static void
+do_data_queue_notify(clientenv *clenv)
+{
+ bool ok = FALSE;
+ /*
+ notify_msg_t msg;
+ */
+
+ /* XXX
+ * We now actually require the lock, since we need to send exactly one
+ * message per new queued mail. Fix accordingly. We however can fill in
+ * the data for the message before sending it to hold the lock for as
+ * short as possible. We also possibly only need it to verify if we're
+ * connected, or to mark the socket disconnected... Since we only need
+ * an atomic send per notification?
+ */
+
+ /*
+ * If we cannot obtain lock, we know that it's already being notified, and
+ * we don't need to do anything.
+ */
+ if (pthread_mutex_lock(&relayd_lock) != 0)
+ return;
+
+ /*
+ * If socket wasn't open yet, attempt to open it. If we cannot, we have
+ * nothing to notify, since the relay daemon most probably doesn't run.
+ */
+ if (relayd_sock == -1) {
+ if ((relayd_sock = do_data_queue_notify_connect()) == -1)
+ goto end;
}
- fdbfreebuf(&fdbrb); /* Internally only frees if not already freed */
- if (ok) err = DATA_OK;
- reply(clenv->fdb, data_msg[err].code, FALSE, data_msg[err].msg);
+ /*
+ * Send a notification packet. If we cannot send it, attempt to reconnect
+ * and send it again, but once only.
+ */
+ for (;;) {
+ if (write(relayd_sock, "N", 1) != 1) {
+ (void) close(relayd_sock);
+ if ((relayd_sock = do_data_queue_notify_connect()) != -1)
+ continue;
+ } else
+ ok = TRUE;
+ break;
+ }
- /* Reset mail state (and free RCPTs) */
- init_clientenv(clenv, FALSE);
+end:
- return (ok);
+ if (!ok)
+ mmsyslog(0, LOGLEVEL,
+ "mmrelayd(8) could not be notified (not running?)");
+
+ (void) pthread_mutex_unlock(&relayd_lock);
}
+/*
+ * Attempt to open the mmrelayd(8) notification socket, returning the
+ * filedescriptor on success, or -1 on failure.
+ */
+static int
+do_data_queue_notify_connect(void)
+{
+ int fd, opt;
+ struct sockaddr_un addr;
+
+ if ((fd = socket(AF_LOCAL, SOCK_DGRAM, 0)) != -1) {
+ opt = (int)BALIGN_CEIL(sizeof(notify_msg_t), 1024);
+ if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(int)) == -1)
+ mmsyslog(0, LOGLEVEL,
+ "do_data_queue_notify_connect() - setsockopt() - (%s)",
+ strerror(errno));
+ mm_memclr(&addr, sizeof(struct sockaddr_un));
+ (void) mm_strncpy(addr.sun_path, CONF.MMRELAYD_SOCKET_PATH, 100);
+ addr.sun_family = AF_UNIX;
+ if ((connect(fd, (struct sockaddr *)&addr,
+ sizeof(struct sockaddr_un))) == -1) {
+ (void) close(fd);
+ fd = -1;
+ } else
+ mmsyslog(0, LOGLEVEL, "Cannot connect to mmrelayd(8) socket "
+ "'%s' (%s)", addr.sun_path, strerror(errno));
+ } else
+ mmsyslog(0, LOGLEVEL, "Cannot create socket descriptor (%s)",
+ strerror(errno));
+ return fd;
+}
/* This is the main function that is called to serve a client.
*/
static int
handleclient(unsigned long id, int fd, clientlistnode *clientlnode,
- struct iface *iface, struct async_clenv *aclenv)
+ struct iface *iface, struct async_clenv *aclenv, void *utdata)
{
- char buffer[1024], ipaddr[20], *tmp;
+ char buffer[1024], ipaddr[64];
int len, state, nstate, timeout, dstatus;
clientenv *clenv;
- struct sockaddr_in *sinaddr;
+ struct server_sockaddr *saddr;
fdbuf *fdb;
int64_t data_in, data_out;
unsigned long rcpt_in, messages_in, time_total;
clenv = NULL;
/* Obtain IP address of client */
- sinaddr = (struct sockaddr_in *)&clientlnode->client;
- if ((tmp = inet_ntoa(sinaddr->sin_addr))) mm_strncpy(ipaddr, tmp, 19);
- else mm_strncpy(ipaddr, "0.0.0.0", 8);
+ saddr = &clientlnode->client;
+ if (inet_ntop(*(SERVER_SOCKADDR_FAMILY(saddr)),
+ SERVER_SOCKADDR_ADDRESS(saddr), ipaddr, 64) == NULL)
+ mm_strcpy(ipaddr, "0.0.0.0");
- if (clientlnode->hostname)
+ if (clientlnode->hostname != NULL)
/* Log user's address and hostname */
- mmsyslog(1, LOGLEVEL, "%08X Connect: [%s] - (%s)", id, ipaddr,
+ mmsyslog(1, LOGLEVEL, "%08lX Connect: [%s] - (%s)", id, ipaddr,
clientlnode->hostname);
else
/* Log user's address only */
- mmsyslog(1, LOGLEVEL, "%08X Connect: [%s]", id, ipaddr);
+ mmsyslog(1, LOGLEVEL, "%08lX Connect: [%s]", id, ipaddr);
time_start = time(NULL);
if ((fdb = fdbopen(&gfdf, &fdbc, fd, 8192, 8192, CONF.BANDWIDTH_IN * 1024,
- CONF.BANDWIDTH_OUT * 1024, timeout, timeout))) {
+ CONF.BANDWIDTH_OUT * 1024, timeout, timeout, FALSE))
+ != NULL) {
/* Allocate our clientenv to share with state functions */
- if ((clenv = alloc_clientenv())) {
+ if ((clenv = alloc_clientenv()) != NULL) {
/* Set some configuration options such as max_rcpts,
* max_mesg_lines, max_mesg_size, hostname...
clenv->fdb = fdb;
clenv->buffer = buffer;
clenv->errors = 0;
+ clenv->messages = clenv->rcpts = 0;
clenv->timeout = timeout;
clenv->c_hostname = clientlnode->hostname;
clenv->c_ipaddr = ipaddr;
clenv->id = id;
clenv->iface = iface;
clenv->aclenv = aclenv;
+ clenv->pgconn = utdata;
+
+ DLIST_INIT(&clenv->rcpt);
+ clenv->helo = clenv->from = NULL;
reply(fdb, 220, FALSE, "%s (%s (%s)) Service ready",
iface->hostname, DAEMON_NAME, DAEMON_VERSION);
state = STATE_ALL;
dstatus = MMS_NORMAL;
- mmstat(&clenv->pstat, STAT_UPDATE, 1, "mmsmtpd.total.connections");
+ mmstat(&clenv->pstat, STAT_UPDATE, 1,
+ "mmsmtpd|total|connections");
mmstat_transact(&clenv->vstat, TRUE);
mmstat(&clenv->vstat, STAT_UPDATE, 1,
- "mmsmtpd.current.connections");
- mmstat(&clenv->vstat, STAT_UPDATE, 1, "mmsmtpd.who.%s",
+ "mmsmtpd|current|connections");
+ mmstat(&clenv->vstat, STAT_UPDATE, 1, "mmsmtpd|who|%s",
clenv->c_ipaddr);
mmstat_transact(&clenv->vstat, FALSE);
/* Main state switcher loop */
for (;;) {
- register int i = 0;
- register int32_t chash;
- register bool valid;
+ uint32_t chash;
+ register struct commandnode *nod;
fdbflushw(fdb);
- if ((len = fdbgets(fdb, buffer, 1000, FALSE)) > -1) {
+ if ((len = fdbgets(fdb, buffer, 1023, FALSE)) > -1) {
/* If there were too many errors, exit accordingly */
if (clenv->errors > CONF.MAX_ERRORS) {
break;
}
/* Verify if command matches an existing one */
- valid = FALSE;
- if ((chash = mm_strpack32(buffer, 4)) != -1) {
- for (i = 0; commands[i].name; i++) {
- if (commands[i].hash == chash) {
- valid = TRUE;
- break;
- }
- }
- }
-
- if (valid) {
+ nod = NULL;
+ if ((chash = mm_strpack32(buffer, 4)) != 0)
+ nod = (struct commandnode *)hashtable_lookup(
+ &command_table, &chash, sizeof(uint32_t));
+ if (nod != NULL) {
register int (*func)(clientenv *);
- mmsyslog(commands[i].loglevel, LOGLEVEL,
- "%08X %s", id, buffer);
+ mmsyslog(nod->command->loglevel, LOGLEVEL,
+ "%08lX < %s", id, buffer);
- if ((func = states[state].functions[i])) {
+ if ((func = states[state].functions[nod->index])
+ != NULL) {
/* Valid command, process it in current state */
nstate = func(clenv);
}
} else {
- mmsyslog(3, LOGLEVEL, "%08X %s", id, buffer);
+ mmsyslog(3, LOGLEVEL, "%08lX < %s", id, buffer);
reply(fdb, 500, FALSE,
"Syntax error or unknown command, type HELP");
REGISTER_ERROR(clenv);
dstatus = MMS_INPUT_ERROR;
if (CONF.STATFAIL_EOF)
mmstat(&clenv->pstat, STAT_UPDATE, 1,
- "mmsmtpd.failed.eof.%s", clenv->c_ipaddr);
+ "mmsmtpd|failed|eof|%s", clenv->c_ipaddr);
break;
} else {
dstatus = MMS_UNKNOWN;
mmstat_transact(&clenv->vstat, TRUE);
mmstat(&clenv->vstat, STAT_UPDATE, -1,
- "mmsmtpd.who.%s", clenv->c_ipaddr);
+ "mmsmtpd|who|%s", clenv->c_ipaddr);
mmstat(&clenv->vstat, STAT_UPDATE, -1,
- "mmsmtpd.current.connections");
+ "mmsmtpd|current|connections");
mmstat_transact(&clenv->vstat, FALSE);
mmstat_transact(&clenv->pstat, TRUE);
mmstat(&clenv->pstat, STAT_UPDATE, messages_in,
- "mmsmtpd.total.messages-in");
+ "mmsmtpd|total|messages-in");
mmstat(&clenv->pstat, STAT_UPDATE, data_in,
- "mmsmtpd.total.in");
+ "mmsmtpd|total|bytes-in");
mmstat(&clenv->pstat, STAT_UPDATE, data_out,
- "mmsmtpd.total.out");
+ "mmsmtpd|total|bytes-out");
mmstat_transact(&clenv->pstat, FALSE);
/* Free our state-shared clenv */
clenv = free_clientenv(clenv);
} else
- mmsyslog(0, LOGLEVEL,
- "* handleclient() - Could not allocate state-shared clenv");
+ DEBUG_PRINTF("handleclient", "alloc_clientenv()");
fdbclose(fdb);
} else
- mmsyslog(0, LOGLEVEL, "* handleclient() - fdbopen()");
+ DEBUG_PRINTF("handleclient", "fdbopen(%d)", fd);
/* Log results */
time_end = time(NULL);
time_total = time_end - time_start;
mmsyslog(1, LOGLEVEL,
- "%08X Closed: [%s] - (Seconds: %lu, Data in: %lld out: %lld, \
-RCPTs: %lu, Messages in: %lu, Status: %s)", id, ipaddr, time_total, data_in,
- data_out, rcpt_in, messages_in, MMS_RSTRING(dstatus));
+ "%08lX Closed: [%s] - (Seconds: %lu, Data in: %lld out: %lld, "
+ "RCPTs: %lu, Messages in: %lu, Status: %s)",
+ id, ipaddr, time_total, data_in, data_out, rcpt_in,
+ messages_in, MMS_RSTRING(dstatus));
return (0);
}
/* mmfd library thread support functions */
+static pthread_mutexattr_t thread_ma;
+
+static void
+thread_init(void)
+{
+ (void) pthread_mutexattr_init(&thread_ma);
+ (void) pthread_mutexattr_settype(&thread_ma, PTHREAD_MUTEX_RECURSIVE);
+}
+
+
static void *
-_pth_mutex_create(void)
+thread_mutex_create(void)
{
struct mutexnode *mnod;
- pth_mutex_acquire(&mutexes_lock, FALSE, NULL);
- mnod = (struct mutexnode *)pool_alloc(&pmutexes, FALSE);
- pth_mutex_release(&mutexes_lock);
+ pthread_mutex_lock(&mutexes_lock);
+ mnod = (struct mutexnode *)pool_alloc(&mutexes_pool, FALSE);
+ pthread_mutex_unlock(&mutexes_lock);
- if (mnod)
- pth_mutex_init(&mnod->mutex);
+ if (mnod != NULL)
+ pthread_mutex_init(&mnod->mutex, &thread_ma);
return ((void *)mnod);
}
static void *
-_pth_mutex_destroy(void *mtx)
+thread_mutex_destroy(void *mtx)
{
- /* struct mutexnode *mnod = mtx; */
+ struct mutexnode *mnod = mtx;
- /* pth_mutex_destroy(&mnod->mutex); */
- pth_mutex_acquire(&mutexes_lock, FALSE, NULL);
+ pthread_mutex_destroy(&mnod->mutex);
+ pthread_mutex_lock(&mutexes_lock);
pool_free(mtx);
- pth_mutex_release(&mutexes_lock);
+ pthread_mutex_unlock(&mutexes_lock);
return (NULL);
}
static void
-_pth_mutex_lock(void *mtx)
+thread_mutex_lock(void *mtx)
{
struct mutexnode *mnod = mtx;
- pth_mutex_acquire(&mnod->mutex, FALSE, NULL);
+ pthread_mutex_lock(&mnod->mutex);
}
static void
-_pth_mutex_unlock(void *mtx)
+thread_mutex_unlock(void *mtx)
{
struct mutexnode *mnod = mtx;
- pth_mutex_release(&mnod->mutex);
+ pthread_mutex_unlock(&mnod->mutex);
}
-static void
-_pth_thread_yield(void)
+static bool
+thread_eintr(void)
{
- pth_yield(NULL);
+ if (errno == EINTR)
+ return TRUE;
+
+ return FALSE;
}
struct async_resquery_msg *amsg = (void *)msg;
amsg->un.res.res = res_query(amsg->un.args.host, amsg->un.args.r_class,
- amsg->un.args.r_type, amsg->un.res.answer, 127);
+ amsg->un.args.r_type, (u_char *)amsg->un.res.answer, 127);
}
static int
a_res_query(clientenv *clenv, const char *dname, int class, int type,
- u_char *answer, int anslen)
+ char *answer, int anslen)
{
struct async_resquery_msg *amsg = (void *)clenv->aclenv->msg;
int res;
return (res);
}
+
+
+/* Here consists of our hostnode expiration thread. It asynchroneously and
+ * occasionally iterating through all the nodes to reset and/or expunge the
+ * expired ones. Doing this here prevents interfering with the normally more
+ * frequent lookups which can be done with hashtable_lookup() in another
+ * thread. We wouln't want those to need to iterate through all the nodes
+ * everytime. We also call a function which ensures to delete any mailbox
+ * files for which an entry exists in the boxdelete database table.
+ */
+/* ARGSUSED */
+static void *
+hosts_expire_thread(void *args)
+{
+ struct hosts_expire_thread_iterator_udata data;
+
+ /* Set the initial timeout to the maximum allowed */
+ data.soonest = CONF.FLOOD_EXPIRES * 60;
+ for (;;) {
+ /* Sleep until it is known that at least one node expired */
+ pthread_sleep(data.soonest);
+ /* Tell our iterator function the current time and the maximum
+ * allowed time to wait to
+ */
+ data.current = time(NULL);
+ data.soonest = CONF.FLOOD_EXPIRES * 60;
+ /* Lock hosts_table, expunge expired nodes and set data.soonest to the
+ * time of the soonest next expireing node
+ */
+ pthread_mutex_lock(&hosts_lock);
+ if (HASHTABLE_NODES(&hosts_table) > 0)
+ hashtable_iterate(&hosts_table, hosts_expire_thread_iterator,
+ &data);
+ pthread_mutex_unlock(&hosts_lock);
+ }
+
+ /* NOTREACHED */
+ pthread_exit(NULL);
+ return NULL;
+}
+
+static bool
+hosts_expire_thread_iterator(hashnode_t *hnod, void *udata)
+{
+ hostnode *nod = (hostnode *)hnod;
+ struct hosts_expire_thread_iterator_udata *data = udata;
+ time_t rem;
+
+ /* If the node expired, free it. For nodes which do not, record the
+ * soonest to expire node.
+ */
+ if ((rem = LR_REMAINS(&nod->lr, data->current)) == 0) {
+ /* Entry expired, free it */
+ hashtable_unlink(&hosts_table, (hashnode_t *)nod);
+ pool_free((pnode_t *)nod);
+ } else {
+ if (data->soonest > rem)
+ data->soonest = rem;
+ }
+
+ return TRUE;
+}
+
+
+/*
+ * This thread performs a verification for entries in the boxdelete table, and
+ * deletes the dangling directories for boxes which have been deleted. This
+ * way we do not need the frontend process to be able to delete arbitrary
+ * files, while being able to provide an administration frontend to delete
+ * mailboxes as needed. We also perform table optimization at regular
+ * intervals.
+ */
+/* ARGSUSED */
+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++) {
+ PGresult *pgres;
+ bool ok;
+
+ (void) pthread_sleep(60);
+
+ /*
+ * 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 ((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;
+ }
+ }
+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;
+ }
+ }
+ 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;
+
+#define OPTIMIZE(s) do { \
+ 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");
+ OPTIMIZE("relayfrom");
+ OPTIMIZE("relaylocal");
+ OPTIMIZE("relayqueue");
+ OPTIMIZE("session");
+ OPTIMIZE("user");
+
+#undef OPTIMIZE
+ }
+ }
+
+ /* NOTREACHED */
+ pthread_exit(NULL);
+ return NULL;
+}