Fixed to comply with new pthread_util pool API
[mmondor.git] / mmsoftware / mmmail / src / mmsmtpd / mmsmtpd.c
CommitLineData
aa2d1861 1/* $Id: mmsmtpd.c,v 1.77 2007/03/17 22:01:34 mmondor Exp $ */
47071c2b
MM
2
3/*
46173fd8 4 * Copyright (C) 2001-2007, Matthew Mondor
47071c2b
MM
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 * must display the following acknowledgement:
17 * This product includes software written by Matthew Mondor.
18 * 4. The name of Matthew Mondor may not be used to endorse or promote
19 * products derived from this software without specific prior written
20 * permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY MATTHEW MONDOR ``AS IS'' AND ANY EXPRESS OR
23 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25 * IN NO EVENT SHALL MATTHEW MONDOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
28 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33
34
35
36
37/* HEADERS */
38
39#include <sys/types.h>
40#include <sys/stat.h>
41#include <fcntl.h>
42#include <stdlib.h>
43#include <stdio.h>
e1089db9 44#include <errno.h>
5f8db290 45#include <string.h> /* strerror(3) */
47071c2b
MM
46
47#include <sys/socket.h>
48#include <netinet/in.h>
49#include <arpa/inet.h>
50#include <arpa/nameser.h>
51#include <resolv.h>
d6eecfe4 52#include <sys/un.h>
47071c2b
MM
53
54#include <syslog.h>
55
47071c2b
MM
56#include <signal.h>
57#include <time.h>
978cad00 58#include <dirent.h>
47071c2b
MM
59
60#include <ctype.h>
61
3dd832e3
MM
62#include <pthread.h>
63
47071c2b
MM
64#include <mmtypes.h>
65#include <mmreadcfg.h>
66#include <mmfd.h>
67#include <mmlist.h>
097ff059
MM
68#include <mmpool.h>
69#include <mmhash.h>
47071c2b 70#include <mmserver.h>
47071c2b
MM
71#include <mmlog.h>
72#include <mmstr.h>
73#include <mmstring.h>
74#include <mmstat.h>
da634739 75#include <mmlimitrate.h>
47071c2b
MM
76
77#include "mmsmtpd.h"
20e5cbf1 78#include "../mmrelayd/mmrelayd.h"
47071c2b
MM
79
80
81
82
46173fd8 83MMCOPYRIGHT("@(#) Copyright (c) 2001-2007\n\
47071c2b 84\tMatthew Mondor. All rights reserved.\n");
aa2d1861 85MMRCSID("$Id: mmsmtpd.c,v 1.77 2007/03/17 22:01:34 mmondor Exp $");
47071c2b
MM
86
87
88
89
90/* GLOBAL VARIABLES */
91/* This stores the global configuration options */
92static CONFIG CONF;
93
94/* Here consists of the commands we support */
95static command commands[] = {
399db776
MM
96 /* LogLevel, Cmd, Args, Description (NULL=unimplemented) */
97 {3, "NOOP", "NOOP", "Does nothing"},
98 {3, "RSET", "RSET", "Resets system to initial state"},
99 {3, "QUIT", "QUIT", "Disconnects, exits"},
100 {3, "HELP", "HELP [<topic>]", "Gives HELP information"},
101 {2, "HELO", "HELO <hostname>", "Permits to authenticate"},
102 {2, "MAIL", "MAIL FROM:<sender>", "Specifies sender of message"},
103 {2, "RCPT", "RCPT TO:<recipient>", "Specifies a recipient"},
104 {3, "DATA", "DATA", "Accepts the message ending with ."},
105 {4, "BEER", NULL, NULL},
106 {0, NULL, NULL, NULL}
47071c2b
MM
107};
108
109/* The system is simple enough that only one state is required, each command
110 * function will perform it's own sanity checking to solidly simulate states.
111 */
112static int (*state_all[])(clientenv *) = {
113 all_noop, /* NOOP */
114 all_rset, /* RSET */
115 all_quit, /* QUIT */
116 all_help, /* HELP */
117 all_helo, /* HELO */
118 all_mail, /* MAIL */
119 all_rcpt, /* RCPT */
120 all_data, /* DATA */
121 all_beer /* BEER */
122};
123
124/* The definitions of our many various states (-: */
4fd4b499 125static const struct state states[] = {
47071c2b
MM
126 {state_all, 0, "Abnormal error"}
127};
128
129/* Used for mmsyslog() */
130static int LOGLEVEL;
131
132/* Used for clenv allocation buffering */
399db776 133static pool_t clenv_pool;
3dd832e3 134static pthread_mutex_t clenv_lock = PTHREAD_MUTEX_INITIALIZER;
47071c2b 135
904cd663 136/* Used for the flood protection cache */
399db776
MM
137static pool_t hosts_pool;
138static hashtable_t hosts_table;
3dd832e3 139static pthread_mutex_t hosts_lock = PTHREAD_MUTEX_INITIALIZER;
47071c2b
MM
140
141/* Used for rcpt allocation buffering */
399db776 142static pool_t rcpt_pool;
3dd832e3 143static pthread_mutex_t rcpt_lock = PTHREAD_MUTEX_INITIALIZER;
47071c2b 144
399db776 145/* Pool used to optimize creating/destroying mmfd mutexes */
3dd832e3 146static pthread_mutex_t mutexes_lock = PTHREAD_MUTEX_INITIALIZER;
399db776
MM
147static pool_t mutexes_pool;
148
149/* For fast command lookup */
150static pool_t command_pool;
151static hashtable_t command_table;
5eb34fba 152
47071c2b
MM
153/* Global bandwidth shaping fdb context */
154static fdbcontext fdbc;
155
aa2d1861
MM
156static bool pq_threadsafe = FALSE;
157
47071c2b 158/* Quick index to RCPT command replies (see mmsmtpd.h for matching defines) */
193955a0 159static const struct reply_messages rcpt_msg[RCPT_MAX] = {
47071c2b
MM
160 {250, "Recipient ok"},
161 {503, "Use MAIL first"},
162 {552, "Too many recipients"},
163 {501, "Invalid address"},
193955a0
MM
164 {501, "Unknown address"},
165 {501, "Relaying denied"},
47071c2b
MM
166 {250, "Recipient already added"},
167 {402, "Mailbox full, try again later"},
168 {402, "Rate exceeded, try again later"},
193955a0 169 {571, "Delivery not authorized, message refused"},
47071c2b
MM
170 {452, "Internal error, contact administrator"}
171};
172
173/* Fast index to DATA replies (see headerfile for matching keywords) */
193955a0 174static const struct reply_messages data_msg[DATA_MAX] = {
47071c2b
MM
175 {354, "Submit message ending with a single ."},
176 {250, "Ok, mail delivered"},
177 {552, "Too much mail data"},
178 {552, "Too many hops"},
179 {452, "Internal error"}
180};
181
5eb34fba
MM
182/* Pth support for mmfd library (that library rocks my world :) */
183static fdfuncs gfdf = {
184 malloc,
185 free,
3dd832e3
MM
186 poll,
187 read,
188 write,
189 pthread_sleep,
190 pthread_usleep,
191 thread_mutex_create,
192 thread_mutex_destroy,
193 thread_mutex_lock,
194 thread_mutex_unlock,
195 NULL,
196 thread_eintr
5eb34fba
MM
197};
198
0376c0d1 199/*
82396359
MM
200 * Used to speed up VALID_ADDR_CHAR()
201 */
46173fd8 202static const int valid_addr_char_table[256] = {
82396359
MM
203 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
204 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
205 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
206 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0,
207 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
208 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
209 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
210 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
211 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
212 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
213 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
214 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
215 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
216 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
217 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
218 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
219};
a4da92fc
MM
220/*
221 * And for VALID_HOST_CHAR()
222 */
46173fd8 223static const int valid_addr_host_table[256] = {
a4da92fc
MM
224 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
225 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
226 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,
227 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
228 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
229 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
230 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
231 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
232 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
233 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
234 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
235 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
236 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
237 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
238 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
239 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
240};
82396359
MM
241
242/*
0376c0d1
MM
243 * Connection to mmrelayd(8) establishment
244 */
aa2d1861
MM
245static int relayd_sock = -1;
246static pthread_mutex_t relayd_lock = PTHREAD_MUTEX_INITIALIZER;
0376c0d1 247
47071c2b
MM
248
249
250
251/* MAIN */
252
253int
254main(int argc, char **argv)
255{
256 uid_t uid;
257 gid_t *gids;
3dd832e3 258 char *conf_file = "/usr/local/etc/mmsmtpd.conf";
47071c2b
MM
259 int ngids, ret = -1;
260 long facility;
47071c2b 261 bool strlist;
f36e2a66
MM
262 cres_t cres;
263 carg_t *cargp;
264 carg_t cargs[] = {
7fd2c069 265 {CAT_STR, CAF_NONE, 1, 255, "LOCK_PATH", CONF.LOCK_PATH},
13794002
MM
266 {CAT_STR, CAF_NONE, 1, 255, "CHROOT_DIR", CONF.CHROOT_DIR},
267 {CAT_STR, CAF_NONE, 1, 255, "PID_PATH", CONF.PID_PATH},
268 {CAT_STR, CAF_NONE, 1, 31, "USER", CONF.USER},
269 {CAT_STR, CAF_NONE, 1, 255, "GROUPS", CONF.GROUPS},
270 {CAT_STR, CAF_NONE, 1, 31, "LOG_FACILITY", CONF.LOG_FACILITY},
271 {CAT_STR, CAF_NONE, 1, 1023, "SERVER_NAMES", CONF.SERVER_NAMES},
272 {CAT_STR, CAF_NONE, 1, 1023, "LISTEN_IPS", CONF.LISTEN_IPS},
46173fd8 273 {CAT_STR, CAF_NONE, 1, 1023, "DB_INFO", CONF.DB_INFO},
5f8db290 274 {CAT_STR, CAF_NONE, 1, 255, "MAIL_DIR", CONF.MAIL_DIR},
d6eecfe4
MM
275 {CAT_STR, CAF_NONE, 1, 255, "MMRELAYD_SOCKET_PATH",
276 CONF.MMRELAYD_SOCKET_PATH},
13794002
MM
277 {CAT_VAL, CAF_NONE, 1, 32, "ASYNC_PROCESSES", &CONF.ASYNC_PROCESSES},
278 {CAT_VAL, CAF_NONE, 1, 9999, "ALLOC_BUFFERS", &CONF.ALLOC_BUFFERS},
279 {CAT_VAL, CAF_NONE, 0, 4, "LOG_LEVEL", &CONF.LOG_LEVEL},
280 {CAT_VAL, CAF_NONE, 1, 65535, "LISTEN_PORT", &CONF.LISTEN_PORT},
281 {CAT_VAL, CAF_NONE, 1, 1000, "MAX_ERRORS", &CONF.MAX_ERRORS},
282 {CAT_VAL, CAF_NONE, 1, 99999, "MAX_IPS", &CONF.MAX_IPS},
283 {CAT_VAL, CAF_NONE, 1, 99999, "MAX_PER_IP", &CONF.MAX_PER_IP},
284 {CAT_VAL, CAF_NONE, 0, 99999, "CONNECTION_RATE",
47071c2b 285 &CONF.CONNECTION_RATE},
c89bdd3b 286 {CAT_VAL, CAF_NONE, 1, 99999, "CONNECTION_PERIOD",
47071c2b 287 &CONF.CONNECTION_PERIOD},
13794002
MM
288 {CAT_VAL, CAF_NONE, 1, 99999, "INPUT_TIMEOUT", &CONF.INPUT_TIMEOUT},
289 {CAT_VAL, CAF_NONE, 0, 99999, "BANDWIDTH_IN", &CONF.BANDWIDTH_IN},
290 {CAT_VAL, CAF_NONE, 0, 99999, "BANDWIDTH_OUT", &CONF.BANDWIDTH_OUT},
291 {CAT_VAL, CAF_NONE, 0, 99999, "GBANDWIDTH_IN", &CONF.GBANDWIDTH_IN},
292 {CAT_VAL, CAF_NONE, 0, 99999, "GBANDWIDTH_OUT", &CONF.GBANDWIDTH_OUT},
293 {CAT_VAL, CAF_NONE, 1, 99999, "MAX_RCPTS", &CONF.MAX_RCPTS},
294 {CAT_VAL, CAF_NONE, 1, 999999, "MAX_DATA_LINES",
47071c2b 295 &CONF.MAX_DATA_LINES},
13794002 296 {CAT_VAL, CAF_NONE, 1, 99999999, "MAX_DATA_SIZE",
47071c2b 297 &CONF.MAX_DATA_SIZE},
13794002
MM
298 {CAT_VAL, CAF_NONE, 1, 999, "MAX_HOPS", &CONF.MAX_HOPS},
299 {CAT_VAL, CAF_NONE, 1, 999999, "FLOOD_MESSAGES",
47071c2b 300 &CONF.FLOOD_MESSAGES},
13794002
MM
301 {CAT_VAL, CAF_NONE, 1, 120, "FLOOD_EXPIRES", &CONF.FLOOD_EXPIRES},
302 {CAT_VAL, CAF_NONE, 50, 999999, "FLOOD_CACHE", &CONF.FLOOD_CACHE},
303 {CAT_BOOL, CAF_NONE, 0, 0, "RESOLVE_HOSTS", &CONF.RESOLVE_HOSTS},
304 {CAT_BOOL, CAF_NONE, 0, 0, "RESOLVE_HELO", &CONF.RESOLVE_HELO},
305 {CAT_BOOL, CAF_NONE, 0, 0, "RESOLVE_MX_MAIL", &CONF.RESOLVE_MX_MAIL},
f2c550b1 306 {CAT_BOOL, CAF_NONE, 0, 0, "RESOLVE_MX_RCPT", &CONF.RESOLVE_MX_RCPT},
13794002
MM
307 {CAT_BOOL, CAF_NONE, 0, 0, "REQUIRE_HELO", &CONF.REQUIRE_HELO},
308 {CAT_BOOL, CAF_NONE, 0, 0, "FLOOD_PROTECTION", &CONF.FLOOD_PROTECTION},
193955a0
MM
309 {CAT_BOOL, CAF_NONE, 0, 0, "STATFAIL_ADDRESS", &CONF.STATFAIL_ADDRESS},
310 {CAT_BOOL, CAF_NONE, 0, 0, "STATFAIL_RELAY", &CONF.STATFAIL_RELAY},
13794002
MM
311 {CAT_BOOL, CAF_NONE, 0, 0, "STATFAIL_FLOOD", &CONF.STATFAIL_FLOOD},
312 {CAT_BOOL, CAF_NONE, 0, 0, "STATFAIL_FULL", &CONF.STATFAIL_FULL},
313 {CAT_BOOL, CAF_NONE, 0, 0, "STATFAIL_TIMEOUT",
47071c2b 314 &CONF.STATFAIL_TIMEOUT},
13794002 315 {CAT_BOOL, CAF_NONE, 0, 0, "STATFAIL_EOF", &CONF.STATFAIL_EOF},
edc0a306 316 {CAT_BOOL, CAF_NONE, 0, 0, "STATFAIL_FILTER", &CONF.STATFAIL_FILTER},
13794002 317 {CAT_BOOL, CAF_NONE, 0, 0, "DELAY_ON_ERROR", &CONF.DELAY_ON_ERROR},
193955a0 318 {CAT_BOOL, CAF_NONE, 0, 0, "RELAYING", &CONF.RELAYING},
13794002 319 {CAT_END, CAF_NONE, 0, 0, NULL, NULL}
47071c2b 320 };
f36e2a66 321 cmap_t cmap[] = {
47071c2b
MM
322 {"LOG_AUTH", LOG_AUTH},
323 {"LOG_AUTHPRIV", LOG_AUTHPRIV},
324 {"LOG_CRON", LOG_CRON},
325 {"LOG_DAEMON", LOG_DAEMON},
326 {"LOG_FTP", LOG_FTP},
327 {"LOG_LPR", LOG_LPR},
328 {"LOG_MAIL", LOG_MAIL},
329 {"LOG_NEWS", LOG_NEWS},
330 {"LOG_SYSLOG", LOG_SYSLOG},
331 {"LOG_USER", LOG_USER},
332 {"LOG_UUCP", LOG_UUCP},
333 {NULL, 0}
334 };
335 struct async_func afuncs[] = {
336 {async_resquery, sizeof(struct async_resquery_msg)},
337 {NULL, 0}
338 };
4fd4b499 339 mmstat_t vstat;
3dd832e3
MM
340 pthread_t hosts_table_thread = NULL;
341 pthread_t mmmail_db_gc_thread = NULL;
342 pthread_attr_t threadattr;
47071c2b
MM
343
344 /* Set defaults */
345 *CONF.CHROOT_DIR = 0;
7fd2c069 346 mm_strcpy(CONF.LOCK_PATH, "/var/run/mmsmtpd.lock");
47071c2b
MM
347 mm_strcpy(CONF.PID_PATH, "/var/run/mmsmtpd.pid");
348 mm_strcpy(CONF.USER, "mmmail");
05b78947 349 mm_strcpy(CONF.GROUPS, "mmmail,mmstat");
47071c2b
MM
350 mm_strcpy(CONF.LOG_FACILITY, "LOG_AUTHPRIV");
351 mm_strcpy(CONF.SERVER_NAMES, "smtp.localhost");
352 mm_strcpy(CONF.LISTEN_IPS, "127.0.0.1");
46173fd8 353 mm_strcpy(CONF.DB_INFO, "dbname=mmmail");
5f8db290 354 mm_strcpy(CONF.MAIL_DIR, "/var/mmmail-dir");
d6eecfe4 355 mm_strcpy(CONF.MMRELAYD_SOCKET_PATH, "/var/run/mmrelayd.sock");
47071c2b
MM
356 CONF.ASYNC_PROCESSES = 3;
357 CONF.ALLOC_BUFFERS = 1;
358 CONF.LOG_LEVEL = 3;
359 CONF.LISTEN_PORT = 25;
360 CONF.MAX_ERRORS = 16;
361 CONF.MAX_IPS = 64;
362 CONF.MAX_PER_IP = 1;
363 CONF.CONNECTION_RATE = 10;
364 CONF.CONNECTION_PERIOD = 30;
365 CONF.INPUT_TIMEOUT = 900;
366 CONF.BANDWIDTH_IN = 16;
367 CONF.BANDWIDTH_OUT = 4;
368 CONF.GBANDWIDTH_IN = 0;
369 CONF.GBANDWIDTH_OUT = 0;
370 CONF.MAX_RCPTS = 16;
371 CONF.MAX_DATA_LINES = 15000;
372 CONF.MAX_DATA_SIZE = 1048576;
373 CONF.MAX_HOPS = 30;
374 CONF.FLOOD_MESSAGES = 20;
375 CONF.FLOOD_EXPIRES = 30;
376 CONF.FLOOD_CACHE = 100;
377 CONF.RESOLVE_HOSTS = FALSE;
46cf2cd5 378 CONF.RESOLVE_HELO = FALSE;
47071c2b 379 CONF.RESOLVE_MX_MAIL = FALSE;
f2c550b1 380 CONF.RESOLVE_MX_RCPT = FALSE;
47071c2b
MM
381 CONF.REQUIRE_HELO = FALSE;
382 CONF.FLOOD_PROTECTION = TRUE;
383 CONF.STATFAIL_ADDRESS = TRUE;
193955a0 384 CONF.STATFAIL_RELAY = TRUE;
47071c2b
MM
385 CONF.STATFAIL_FLOOD = TRUE;
386 CONF.STATFAIL_FULL = TRUE;
387 CONF.STATFAIL_TIMEOUT = TRUE;
1a5bbe01 388 CONF.STATFAIL_EOF = TRUE;
edc0a306 389 CONF.STATFAIL_FILTER = TRUE;
e334174e 390 CONF.DELAY_ON_ERROR = FALSE;
193955a0 391 CONF.RELAYING = FALSE;
47071c2b
MM
392
393 /* Advertize */
394 printf("\r\n+++ %s (%s)\r\n\r\n", DAEMON_NAME, DAEMON_VERSION);
395
396 /* Read config file */
397 if (argc == 2)
398 conf_file = argv[1];
13794002 399 if (!mmreadcfg(&cres, cargs, conf_file)) {
47071c2b
MM
400 /* Error parsing configuration file, report which */
401 printf("\nError parsing '%s'\n", conf_file);
402 printf("Error : %s\n", mmreadcfg_strerr(cres.CR_Err));
403 if (*(cres.CR_Data)) printf("Data : %s\n", cres.CR_Data);
13794002
MM
404 if ((cargp = cres.CR_Keyword) != NULL) {
405 printf("Keyword: %s\n", cargp->CA_Keyword);
47071c2b
MM
406 printf("Minimum: %ld\n", cargp->CA_Min);
407 printf("Maximum: %ld\n", cargp->CA_Max);
408 }
13794002
MM
409 if (cres.CR_Line != -1)
410 printf("Line : %d\n", cres.CR_Line);
47071c2b
MM
411 printf("\n");
412 exit(-1);
413 }
414
7fd2c069
MM
415 /* Ensure that only a single instance with same configuration runs */
416 if (lock_check(CONF.LOCK_PATH) == -1) {
417 printf("\nCouldn't lock file '%s' (already running?)\n\n",
418 CONF.LOCK_PATH);
419 exit(-1);
420 }
421
47071c2b
MM
422 /* Post parsing */
423 if (!mmmapstring(cmap, CONF.LOG_FACILITY, &facility)) {
424 printf("\nUnknown syslog facility %s\n\n", CONF.LOG_FACILITY);
425 exit(-1);
426 }
427 LOGLEVEL = CONF.LOG_LEVEL;
428 /* Translate to numbers the user and group we were told to run as */
429 if ((uid = mmgetuid(CONF.USER)) == -1) {
430 printf("\nUnknown user '%s'\n\n", CONF.USER);
431 exit(-1);
432 }
433 if (!(gids = mmgetgidarray(&ngids, CONF.GROUPS)) == -1) {
434 printf("\nOne of following groups unknown: '%s'\n\n", CONF.GROUPS);
435 exit(-1);
436 }
47071c2b 437
5f8db290
MM
438 if (*CONF.MAIL_DIR != '/') {
439 printf("\nMAIL_DIR must be an absolute pathname to a directory\n\n");
440 exit(-1);
441 } else {
5f8db290
MM
442 struct stat st;
443
444 if (stat(CONF.MAIL_DIR, &st) == -1) {
445 printf("\nMAIL_DIR could not be found: '%s'\n\n", CONF.MAIL_DIR);
446 exit(-1);
447 }
448 if (!S_ISDIR(st.st_mode)) {
449 printf("\nMAIL_DIR not a directory: '%s'\n\n", CONF.MAIL_DIR);
450 exit(-1);
451 }
5f8db290
MM
452 }
453
47071c2b
MM
454 /* Finally init everything */
455 openlog(DAEMON_NAME, LOG_PID | LOG_NDELAY, facility);
456
457#ifndef NODROPPRIVS
458 if ((getuid())) {
459 printf("\nOnly the super user may start this daemon\n\n");
917e9cbb 460 syslog(LOG_NOTICE, "* Only superuser can start me");
47071c2b
MM
461 exit(-1);
462 }
463#else /* NODROPPRIVS */
464 if ((getuid()) == 0) {
465 printf("\nCompiled with NODROPPRIVS, refusing to run as uid 0\n\n");
917e9cbb 466 syslog(LOG_NOTICE, "* NODROPPRIVS, refusing to run as uid 0");
47071c2b
MM
467 exit(-1);
468 }
469#endif /* !NODROPPRIVS */
470
917e9cbb
MM
471 /* In case we chroot(2), the following is a good idea to execute first */
472 mmstat_initialize();
0376ca74
MM
473 mmstat_init(&vstat, TRUE, TRUE);
474 mmstat_transact(&vstat, TRUE);
8303aa4f
MM
475 mmstat(&vstat, STAT_DELETE, 0, "mmsmtpd|current|connections");
476 mmstat(&vstat, STAT_DELETE, 0, "mmsmtpd|who|*");
0376ca74 477 mmstat_transact(&vstat, FALSE);
47071c2b
MM
478 res_init();
479
480 make_daemon(CONF.PID_PATH, CONF.CHROOT_DIR);
917e9cbb
MM
481
482 /* After possible chroot(2) */
47071c2b 483 async_init(afuncs, CONF.ASYNC_PROCESSES, uid, gids, ngids);
47071c2b 484
917e9cbb 485 /* Things which shouldn't be part of the async pool processes */
3dd832e3 486 async_init_pthread();
47071c2b
MM
487
488 /* Allocate necessary pools */
5eb34fba 489 /* Client nodes */
46173fd8
MM
490 pool_init(&clenv_pool, "clenv_pool", malloc, free,
491 clientenv_constructor, clientenv_destructor,
492 sizeof(clientenv), (65536 * CONF.ALLOC_BUFFERS) / sizeof(clientenv),
493 0, 0);
47071c2b 494 /* RCPT nodes */
ed396790
MM
495 pool_init(&rcpt_pool, "rcpt_pool", malloc, free, NULL, NULL,
496 sizeof(rcptnode),
7a56f31f 497 (16384 * CONF.ALLOC_BUFFERS) / sizeof(rcptnode), 0, 0);
5eb34fba 498 /* Mutexes pool for mmfd */
ed396790 499 pool_init(&mutexes_pool, "mutexes_pool", malloc, free, NULL, NULL,
343b3a6c 500 sizeof(struct mutexnode),
7a56f31f 501 (16384 * CONF.ALLOC_BUFFERS) / sizeof(struct mutexnode), 0, 0);
3dd832e3
MM
502
503 pthread_attr_init(&threadattr);
46173fd8 504 pthread_attr_setdetachstate(&threadattr, PTHREAD_CREATE_JOINABLE);
3dd832e3 505
47071c2b 506 /* Rate nodes */
7a56f31f 507 if (CONF.FLOOD_PROTECTION) {
ed396790
MM
508 pool_init(&hosts_pool, "hosts_pool", malloc, free, NULL, NULL,
509 sizeof(hostnode), CONF.FLOOD_CACHE, 1, 1);
a95888ea
MM
510 hashtable_init(&hosts_table, "hosts_table", CONF.FLOOD_CACHE, 1,
511 malloc, free, mm_memcmp, mm_memhash32, FALSE);
3dd832e3
MM
512 pthread_create(&hosts_table_thread, &threadattr, hosts_expire_thread,
513 NULL);
7a56f31f 514 }
46173fd8 515 /* Launch files gc cleaning thread */
3dd832e3 516 pthread_create(&mmmail_db_gc_thread, &threadattr, db_gc_thread, NULL);
47071c2b 517 /* mmstr nodes */
7a56f31f 518 strlist = mmstrinit(malloc, free, 65536 * CONF.ALLOC_BUFFERS);
47071c2b 519
aa2d1861
MM
520#if (PGSQL_VERSION >= 820)
521 pq_threadsafe = (bool)PQisthreadsafe();
522#else
523 pq_threadsafe = FALSE;
524#endif
525
399db776
MM
526 if (hash_commands(commands, 4) && POOL_VALID(&clenv_pool) &&
527 POOL_VALID(&rcpt_pool) && POOL_VALID(&mutexes_pool) && strlist &&
528 (!CONF.FLOOD_PROTECTION || (POOL_VALID(&hosts_pool) &&
529 HASHTABLE_VALID(&hosts_table) &&
530 hosts_table_thread != NULL))) {
3dd832e3 531 thread_init();
5eb34fba
MM
532 fdbcinit(&gfdf, &fdbc, CONF.GBANDWIDTH_IN * 1024,
533 CONF.GBANDWIDTH_OUT * 1024);
917e9cbb
MM
534
535 tcp_server("402 Server too busy, try again\r\n",
536 CONF.SERVER_NAMES, CONF.LISTEN_IPS, uid, gids, ngids,
537 CONF.MAX_IPS, CONF.MAX_PER_IP, CONF.CONNECTION_RATE,
538 CONF.CONNECTION_PERIOD, CONF.INPUT_TIMEOUT,
539 CONF.LISTEN_PORT, CONF.RESOLVE_HOSTS, handleclient);
540
541 mmfreegidarray(gids);
542 ret = 0;
917e9cbb 543 } else {
47071c2b 544 printf("\nOut of memory\n\n");
917e9cbb
MM
545 syslog(LOG_NOTICE, "* Out of memory");
546 }
47071c2b 547
978cad00
MM
548 if (strlist)
549 mmstrexit();
399db776 550 if (hosts_table_thread != NULL) {
aa2d1861
MM
551 pthread_kill(hosts_table_thread, SIGTERM);
552 pthread_join(hosts_table_thread, NULL);
904cd663 553 }
978cad00 554 if (mmmail_db_gc_thread != NULL) {
aa2d1861
MM
555 pthread_kill(mmmail_db_gc_thread, SIGTERM);
556 pthread_join(mmmail_db_gc_thread, NULL);
978cad00 557 }
399db776
MM
558 if (HASHTABLE_VALID(&command_table))
559 hashtable_destroy(&command_table, FALSE);
560 if (POOL_VALID(&command_pool))
561 pool_destroy(&command_pool);
562 if (HASHTABLE_VALID(&hosts_table))
563 hashtable_destroy(&hosts_table, FALSE);
564 if (POOL_VALID(&mutexes_pool))
565 pool_destroy(&mutexes_pool);
566 if (POOL_VALID(&hosts_pool))
567 pool_destroy(&hosts_pool);
568 if (POOL_VALID(&rcpt_pool))
569 pool_destroy(&rcpt_pool);
570 if (POOL_VALID(&clenv_pool))
571 pool_destroy(&clenv_pool);
47071c2b
MM
572
573 fdbcdestroy(&fdbc);
42dd3825 574 kill(0, SIGTERM);
47071c2b
MM
575 exit(ret);
576}
577
578
579/* Here consists of our command functions */
580
581
582static int
583all_noop(clientenv *clenv)
584{
5eb34fba 585 reply(clenv->fdb, 250, FALSE, "Ok, nothing performed");
47071c2b 586
5eb34fba 587 return (STATE_CURRENT);
47071c2b
MM
588}
589
590
591static int
592all_rset(clientenv *clenv)
593{
594 int nextstate = STATE_CURRENT;
595 fdbuf *fdb = clenv->fdb;
47071c2b
MM
596
597 if (clenv->buffer[4] == 0) {
5eb34fba 598 if (!init_clientenv(clenv, TRUE))
47071c2b 599 nextstate = STATE_ERROR;
5eb34fba
MM
600 else
601 reply(fdb, 250, FALSE, "Reset state");
602 } else {
603 reply(fdb, 550, FALSE, "Command syntax error");
e334174e 604 REGISTER_ERROR(clenv);
47071c2b
MM
605 }
606
607 return (nextstate);
608}
609
610
611static int
612all_quit(clientenv *clenv)
613{
5eb34fba
MM
614 reply(clenv->fdb, 221, FALSE, "%s closing connection",
615 clenv->iface->hostname);
47071c2b 616
5eb34fba 617 return (STATE_END);
47071c2b
MM
618}
619
620
621static int
622all_help(clientenv *clenv)
623{
5eb34fba 624 int col;
47071c2b
MM
625 fdbuf *fdb = clenv->fdb;
626 char *args[3], *cmdline = clenv->buffer, *tmp;
627
628 /* First check if a topic was specified */
629 if ((col = mm_straspl(args, cmdline, 2)) == 2) {
399db776
MM
630 u_int32_t chash;
631 struct commandnode *nod;
47071c2b
MM
632
633 /* Help requested on a topic */
399db776
MM
634 nod = NULL;
635 if ((chash = mm_strpack32(args[1], 4)) != 0)
636 nod = (struct commandnode *)hashtable_lookup(&command_table,
637 &chash, sizeof(u_int32_t));
47071c2b 638 col = 0;
399db776
MM
639 if (nod != NULL) {
640 reply(fdb, 214, TRUE, nod->command->args);
641 reply(fdb, 214, TRUE, " %s", nod->command->desc);
47071c2b
MM
642 col = 2;
643 }
644
5f8db290 645 if (col > 0)
5eb34fba
MM
646 reply(fdb, 214, FALSE, "End of HELP information");
647 else {
648 reply(fdb, 504, FALSE, "Unknown HELP topic");
e334174e 649 REGISTER_ERROR(clenv);
47071c2b
MM
650 }
651
652 } else {
653 register int i;
654
655 /* No, display the topics */
5eb34fba
MM
656 reply(fdb, 214, TRUE, "Available topics:");
657 fdbwrite(fdb, "214-", 4);
658 col = 1;
5f8db290
MM
659 for (i = 0; (tmp = commands[i].name) != NULL; i++) {
660 if (commands[i].desc != NULL) {
661 if (col == 0)
662 fdbwrite(fdb, "\r\n214-", 6);
5eb34fba 663 col++;
5f8db290
MM
664 if (col > 4)
665 col = 0;
5eb34fba 666 fdbprintf(fdb, " %s", tmp);
47071c2b 667 }
47071c2b 668 }
5eb34fba 669 fdbwrite(fdb, "\r\n", 2);
47071c2b 670
5eb34fba
MM
671 reply(fdb, 214, TRUE, "For more information, use HELP <topic>");
672 reply(fdb, 214, FALSE, "End of HELP information");
47071c2b
MM
673 }
674
5eb34fba 675 return (STATE_CURRENT);
47071c2b
MM
676}
677
678
679static int
680all_helo(clientenv *clenv)
681{
47071c2b
MM
682 fdbuf *fdb = clenv->fdb;
683 char *args[3], *cmdline = clenv->buffer;
684
685 if ((mm_straspl(args, cmdline, 2)) == 2) {
1a57cc86 686 if (clenv->helo == NULL) {
46cf2cd5 687 if (valid_host(clenv, args[1],
f2c550b1
MM
688 CONF.RESOLVE_HELO ? HOST_RES : HOST_NORES, TRUE,
689 TRUE)) {
5eb34fba 690 if ((clenv->helo = mmstrdup(args[1])) == NULL)
e89b4e26 691 DEBUG_PRINTF("all_helo", "mmstrdup(%s)", args[1]);
5eb34fba 692 reply(fdb, 250, FALSE, "%s ok", clenv->iface->hostname);
47071c2b 693 } else {
5eb34fba 694 reply(fdb, 501, FALSE, "Invalid hostname");
e334174e 695 REGISTER_ERROR(clenv);
47071c2b
MM
696 }
697 } else {
5eb34fba 698 reply(fdb, 503, FALSE, "Duplicate HELO, use RSET or proceed");
e334174e 699 REGISTER_ERROR(clenv);
47071c2b
MM
700 }
701 } else {
5eb34fba 702 reply(fdb, 550, FALSE, "Command syntax error");
e334174e 703 REGISTER_ERROR(clenv);
47071c2b
MM
704 }
705
5eb34fba 706 return (STATE_CURRENT);
47071c2b
MM
707}
708
709
710static int
711all_mail(clientenv *clenv)
712{
713 int nextstate = STATE_CURRENT;
714 fdbuf *fdb = clenv->fdb;
a4da92fc 715 char addr[128];
47071c2b
MM
716 bool valid;
717
5f8db290 718 if (!CONF.REQUIRE_HELO || clenv->helo != NULL) {
47071c2b 719
5f8db290 720 if (clenv->from == NULL) {
47071c2b
MM
721
722 valid = FALSE;
5f8db290 723 if ((mm_strncasecmp(" FROM:<>", &clenv->buffer[4], 8)) == 0) {
47071c2b
MM
724 /* Some systems use empty MAIL FROM like this, make sure
725 * that IP address or hostname is allowed to do this.
3dd832e3
MM
726 * If so, we also want to make sure not to perform any type
727 * of envelope based filtering for this post.
47071c2b 728 */
46173fd8 729 if ((valid = check_nofrom(clenv)))
5f8db290 730 *addr = '\0';
3dd832e3
MM
731 clenv->nofrom = TRUE;
732 } else {
a4da92fc 733 valid = valid_address(clenv, addr, 128, clenv->buffer,
46cf2cd5 734 (CONF.RESOLVE_MX_MAIL) ? HOST_RES_MX : HOST_NORES);
3dd832e3
MM
735 clenv->nofrom = FALSE;
736 }
47071c2b
MM
737
738 if (valid) {
5f8db290 739 if ((clenv->from = (char *)mmstrdup(addr)) != NULL)
5eb34fba
MM
740 reply(fdb, 250, FALSE, "Sender ok");
741 else
47071c2b
MM
742 nextstate = STATE_ERROR;
743 } else {
5eb34fba 744 reply(fdb, 501, FALSE, "Invalid address");
e334174e 745 REGISTER_ERROR(clenv);
47071c2b
MM
746 }
747
748 } else {
5eb34fba 749 reply(fdb, 503, FALSE, "Sender already specified");
e334174e 750 REGISTER_ERROR(clenv);
47071c2b
MM
751 }
752
753 } else {
5eb34fba 754 reply(fdb, 503, FALSE, "Use HELO first");
e334174e 755 REGISTER_ERROR(clenv);
47071c2b
MM
756 }
757
758 return (nextstate);
759}
760
761
762static int
763all_rcpt(clientenv *clenv)
764{
765 int nextstate = STATE_CURRENT;
766 fdbuf *fdb = clenv->fdb;
767 char addr[64], foraddr[64], *line = clenv->buffer;
768 int reason;
edc0a306 769 struct box_info boxinfo;
47071c2b 770 u_int64_t ahash;
193955a0 771 bool valid, relay;
47071c2b
MM
772
773 /* I have opted for an elimination process here as there are many cases
774 * which can cause an RCPT to be refused, and alot of indenting was to
775 * be avoided for clarity. Functions could also be used but it has not
776 * been necessary this far, and we want the code performance to be optimal.
777 */
193955a0 778 relay = FALSE;
47071c2b 779
5f8db290 780 if (clenv->from == NULL) {
47071c2b 781 reason = RCPT_NOFROM;
193955a0 782 goto end;
47071c2b
MM
783 }
784
785 /* First make sure to not allow more RCPTs than CONF.MAX_RCPTS */
193955a0
MM
786 if (!(DLIST_NODES(&clenv->rcpt) < CONF.MAX_RCPTS)) {
787 reason = RCPT_MANY;
788 if (CONF.STATFAIL_FLOOD)
789 mmstat(&clenv->pstat, STAT_UPDATE, 1,
790 "mmsmtpd|failed|flood|rcpt|%s|%s",
791 clenv->c_ipaddr, clenv->from);
792 goto end;
793 }
794
795 /* Only continue if address seems valid */
a4da92fc 796 if (!valid_address(clenv, addr, 64, line, HOST_NORES)) {
193955a0
MM
797 reason = RCPT_INVALID;
798 goto end;
47071c2b
MM
799 }
800
801 /* Verify if existing address, if it isn't verify for any alias
802 * matching it and of course check for address validity again for
803 * safety. This way we make sure that an alias pattern does not over-
804 * ride an existing address, and that we only archive a message into
f2c550b1
MM
805 * an existing mailbox. <addr> is not modified if there exist no alias for
806 * the address. Otherwise, <foraddr> keeps the original address.
47071c2b 807 */
193955a0 808 valid = FALSE;
f2c550b1 809 (void) mm_strcpy(foraddr, addr);
46173fd8
MM
810 if (!(valid = local_address(clenv, &boxinfo, addr))) {
811 if (check_alias(clenv, addr)) {
812 if (!(valid = local_address(clenv, &boxinfo, addr)))
193955a0
MM
813 mmsyslog(0, LOGLEVEL, "Invalid alias address (%s)",
814 addr);
47071c2b
MM
815 }
816 }
193955a0
MM
817 if (!valid)
818 reason = RCPT_UNKNOWN;
193955a0
MM
819 if (CONF.RELAYING && !valid) {
820 /* Address is not local. If relaying is allowed, we must be
821 * able to verify that the address indeed belongs to a
822 * non-local domain, and if so, verify that the sender is
823 * allowed to relay messages. If it belongs to a local domain,
824 * we must treat it as invalid local address, however.
825 */
f2c550b1
MM
826 if ((valid = address_relay_allow(clenv, &reason, foraddr))) {
827 if (CONF.RESOLVE_MX_RCPT) {
828 /* We know that the address is in a valid format, but we are
829 * now required to verify that the hostname has an MX record.
830 */
831 char *domain;
832
833 for (domain = foraddr; *domain != '@'; domain++) ;
834 domain++;
835 if (!valid_host(clenv, domain, HOST_RES_MX, FALSE, FALSE)) {
836 reason = RCPT_INVALID;
837 goto end;
838 }
839 }
193955a0 840 relay = TRUE;
f2c550b1 841 }
193955a0 842 }
193955a0
MM
843 if (!valid) {
844 switch (reason) {
845 case RCPT_RELAY:
846 if (CONF.STATFAIL_RELAY) {
847 mmstat(&clenv->pstat, STAT_UPDATE, 1,
848 "mmsmtpd|failed|relay|%s|%s|%s",
849 clenv->c_ipaddr, clenv->from, foraddr);
850 }
851 break;
852 case RCPT_UNKNOWN:
853 if (CONF.STATFAIL_ADDRESS) {
edc0a306 854 mmstat(&clenv->pstat, STAT_UPDATE, 1,
193955a0
MM
855 "mmsmtpd|failed|address|%s|%s|%s",
856 clenv->c_ipaddr, clenv->from, addr);
857 }
858 break;
edc0a306 859 }
193955a0 860 goto end;
edc0a306
MM
861 }
862
193955a0
MM
863 /* These only apply to local addresses */
864 if (!relay) {
3dd832e3
MM
865 /*
866 * Ensure to observe allow filters if any set for box, except for mail
867 * with an empty FROM address from allowed servers
868 */
869 if (boxinfo.filter && !clenv->nofrom) {
46173fd8
MM
870 if (!box_filter_allow(clenv, addr, clenv->from,
871 boxinfo.filter_type)) {
193955a0
MM
872 reason = RCPT_FILTER;
873 if (CONF.STATFAIL_FILTER)
874 mmstat(&clenv->pstat, STAT_UPDATE, 1,
875 "mmsmtpd|failed|filter|%s|%s|%s",
876 clenv->c_ipaddr, clenv->from, addr);
877 goto end;
878 }
879 }
880 /* Make sure mailbox quota limits are respected */
46173fd8
MM
881 if (!((boxinfo.size < boxinfo.max_size) &&
882 (boxinfo.msgs < boxinfo.max_msgs))) {
47071c2b 883 mmsyslog(0, LOGLEVEL, "%s mailbox full (%ld,%ld %ld,%ld)",
edc0a306
MM
884 addr, boxinfo.max_size, boxinfo.size, boxinfo.max_msgs,
885 boxinfo.msgs);
47071c2b
MM
886 reason = RCPT_FULL;
887 if (CONF.STATFAIL_FULL)
888 mmstat(&clenv->pstat, STAT_UPDATE, 1,
8303aa4f 889 "mmsmtpd|failed|full|%s", addr);
193955a0 890 goto end;
47071c2b
MM
891 }
892 }
893
894 /* Make sure that we only allow one RCPT per mailbox (alias already
895 * redirected to it)
46173fd8 896 * XXX We probably should use a real hash table for RCPTs
47071c2b 897 */
193955a0 898 {
47071c2b 899 register rcptnode *rnode;
47071c2b 900
917e9cbb 901 ahash = mm_strhash64(addr);
d2558e27 902 DLIST_FOREACH(&clenv->rcpt, rnode) {
47071c2b 903 if (rnode->hash == ahash) {
47071c2b 904 reason = RCPT_EXISTS;
193955a0 905 goto end;
47071c2b 906 }
47071c2b
MM
907 }
908 }
909
904cd663
MM
910 /* If CONF.FLOOD_PROTECTION is TRUE, make sure that we respect the rate
911 * of CONF.FLOOD_MESSAGES within CONF.FLOOD_EXPIRES for this client.
47071c2b 912 */
193955a0 913 if (CONF.FLOOD_PROTECTION) {
399db776 914 register hostnode *hnod;
904cd663
MM
915 register size_t len;
916 register char *entry;
47071c2b 917
904cd663
MM
918 if (clenv->c_hostname != NULL)
919 entry = clenv->c_hostname;
920 else
921 entry = clenv->c_ipaddr;
922 len = mm_strlen(entry);
47071c2b 923
193955a0 924 valid = TRUE;
3dd832e3 925 pthread_mutex_lock(&hosts_lock);
399db776
MM
926 /* First acquire our hostnode, or create it if required */
927 if ((hnod = (hostnode *)hashtable_lookup(&hosts_table, entry, len + 1))
da634739 928 == NULL) {
904cd663 929 /* Create a new entry */
399db776
MM
930 if ((hnod = (hostnode *)pool_alloc(&hosts_pool, FALSE)) != NULL) {
931 mm_memcpy(hnod->host, entry, len + 1);
da634739
MM
932 LR_INIT(&hnod->lr, CONF.FLOOD_MESSAGES,
933 CONF.FLOOD_EXPIRES * 60, time(NULL));
399db776 934 hashtable_link(&hosts_table, (hashnode_t *)hnod, entry,
0fb32571 935 len + 1, FALSE);
904cd663
MM
936 } else {
937 valid = FALSE;
938 reason = RCPT_FLOOD;
95c67efd 939 mmsyslog(0, LOGLEVEL, "FLOOD_CACHE not large enough");
904cd663 940 }
47071c2b 941 }
da634739
MM
942 if (valid) {
943 /* Check and update limits */
944 if (!lr_allow(&hnod->lr, 1, 0, FALSE)) {
945 valid = FALSE;
946 reason = RCPT_FLOOD;
947 mmsyslog(0, LOGLEVEL,
e89b4e26
MM
948 "%08X Considered flood and rejected (%ld message(s) "
949 "within last %ld minute(s))",
950 clenv->id, LR_POSTS(&hnod->lr), CONF.FLOOD_EXPIRES);
da634739
MM
951 }
952 }
3dd832e3 953 pthread_mutex_unlock(&hosts_lock);
47071c2b 954
193955a0
MM
955 if (!valid) {
956 if (CONF.STATFAIL_FLOOD)
957 mmstat(&clenv->pstat, STAT_UPDATE, 1,
958 "mmsmtpd|failed|flood|message|%s|%s|%s",
f2c550b1 959 clenv->c_ipaddr, clenv->from, addr);
193955a0
MM
960 goto end;
961 }
47071c2b
MM
962 }
963
193955a0
MM
964 /* Finally append new RCPT node to list */
965 {
47071c2b
MM
966 register rcptnode *rnode;
967
968 reason = RCPT_ERROR;
3dd832e3 969 pthread_mutex_lock(&rcpt_lock);
399db776 970 rnode = (rcptnode *)pool_alloc(&rcpt_pool, FALSE);
3dd832e3 971 pthread_mutex_unlock(&rcpt_lock);
da634739 972 if (rnode != NULL) {
193955a0 973 mm_strcpy(rnode->address, (relay ? foraddr : addr));
47071c2b
MM
974 mm_strcpy(rnode->foraddress, foraddr);
975 rnode->hash = ahash;
193955a0 976 rnode->relay = relay;
d2558e27 977 DLIST_APPEND(&clenv->rcpt, (node_t *)rnode);
47071c2b
MM
978 reason = RCPT_OK;
979 clenv->rcpts++;
980 } else {
e89b4e26 981 DEBUG_PRINTF("all_rcpt", "pool_alloc(rcpt_pool)");
47071c2b
MM
982 nextstate = STATE_ERROR;
983 }
984 }
985
193955a0
MM
986end:
987
47071c2b
MM
988 /* Reply with appropriate message */
989 if (reason != RCPT_OK)
e334174e 990 REGISTER_ERROR(clenv);
5eb34fba 991 reply(fdb, rcpt_msg[reason].code, FALSE, rcpt_msg[reason].msg);
47071c2b
MM
992
993 return (nextstate);
994}
995
996
997static int
998all_data(clientenv *clenv)
999{
1000 int nextstate = STATE_CURRENT;
1001 fdbuf *fdb = clenv->fdb;
1002
5f8db290
MM
1003 if (clenv->buffer[4] == '\0') {
1004 if (clenv->from != NULL) {
d2558e27 1005 if (DLIST_NODES(&clenv->rcpt) > 0) {
47071c2b
MM
1006 if (!do_data(clenv))
1007 nextstate = STATE_ERROR;
1008 else
1009 clenv->messages++;
1010 } else {
5eb34fba 1011 reply(fdb, 502, FALSE, "Use RCPT first");
e334174e 1012 REGISTER_ERROR(clenv);
47071c2b
MM
1013 }
1014 } else {
5eb34fba 1015 reply(fdb, 503, FALSE, "Use MAIL and RCPT first");
e334174e 1016 REGISTER_ERROR(clenv);
47071c2b
MM
1017 }
1018 } else {
5eb34fba 1019 reply(fdb, 550, FALSE, "Command syntax error");
e334174e 1020 REGISTER_ERROR(clenv);
47071c2b
MM
1021 }
1022
1023 return (nextstate);
1024}
1025
1026
1027static int
1028all_beer(clientenv *clenv)
1029{
5eb34fba 1030 reply(clenv->fdb, 420, FALSE, "Here, enjoy!");
47071c2b 1031
5eb34fba 1032 return (STATE_CURRENT);
47071c2b
MM
1033}
1034
1035
1036
1037
399db776
MM
1038/* Used to initialize command hash table */
1039static bool
1040hash_commands(struct command *cmd, size_t min)
1041{
1042 int i;
1043
1044 /* We do not care for any unfreed resources, the program will free them
1045 * and exit if we return FALSE.
1046 */
ed396790 1047 if (!pool_init(&command_pool, "command_pool", malloc, free, NULL, NULL,
343b3a6c 1048 sizeof(struct commandnode), 64, 1, 0) ||
a95888ea 1049 !hashtable_init(&command_table, "command_table", 64, 1, malloc,
399db776
MM
1050 free, commandnode_keycmp, commandnode_keyhash, TRUE))
1051 return FALSE;
1052
1053 for (i = 0; cmd->name != NULL; cmd++, i++) {
1054 struct commandnode *nod;
1055
1056 if ((nod = (struct commandnode *)pool_alloc(&command_pool, FALSE))
1057 == NULL)
1058 return FALSE;
1059 if ((nod->hash = mm_strpack32(cmd->name, min)) == 0)
1060 return FALSE;
1061 nod->command = cmd;
1062 nod->index = i;
1063 if (!hashtable_link(&command_table, (hashnode_t *)nod, &nod->hash,
0fb32571 1064 sizeof(u_int32_t), TRUE)) {
e89b4e26 1065 DEBUG_PRINTF("hash_commands", "hashtable_link(%s)", cmd->name);
5f8db290 1066 return FALSE;
399db776 1067 }
47071c2b 1068 }
399db776
MM
1069
1070 return TRUE;
1071}
1072
1073
1074/* A quick hashtable_hash() replacement which already deals with unique
1075 * 32-bit values
1076 */
1077/* ARGSUSED */
1078static u_int32_t
1079commandnode_keyhash(const void *data, size_t len)
1080{
1081 return *((u_int32_t *)data);
1082}
1083
1084
1085/* A quick memcmp() replacement which only needs to compare two 32-bit values
1086 */
1087/* ARGSUSED */
1088static int
1089commandnode_keycmp(const void *src, const void *dst, size_t len)
1090{
1091 return *((u_int32_t *)src) - *((u_int32_t *)dst);
47071c2b
MM
1092}
1093
1094
1095/* Function used to return standard RFC result strings to the client,
5eb34fba
MM
1096 * and returns FALSE on error.
1097 * NOTE: As we are no longer calling write() directly, but fdbprintf()
1098 * buffering function instead, it is no longer necessary to check the reply()
1099 * return value each time it is called. We made sure to ignore SIGPIPE,
1100 * and we let the main state switcher loop check connection state via
1101 * fdbgets().
47071c2b
MM
1102 */
1103static bool
1104reply(fdbuf *fdb, int code, bool cont, const char *fmt, ...)
1105{
1106 char buf[1024];
1107 va_list arg_ptr;
1108 bool err = TRUE;
1109
1110 *buf = 0;
1111 va_start(arg_ptr, fmt);
1112 vsnprintf(buf, 1023, fmt, arg_ptr);
1113 va_end(arg_ptr);
1114
5f8db290
MM
1115 if (cont)
1116 err = fdbprintf(fdb, "%d-%s\r\n", code, buf);
1117 else
1118 err = fdbprintf(fdb, "%d %s\r\n", code, buf);
47071c2b
MM
1119
1120 mmsyslog(3, LOGLEVEL, "> %d (%s)", code, buf);
1121
1122 return (err);
1123}
1124
1125
46173fd8
MM
1126static bool
1127clientenv_constructor(pnode_t *pn)
1128{
1129 clientenv *clenv = (clientenv *)pn;
1130
1131 mmstat_init(&clenv->vstat, TRUE, TRUE);
1132 mmstat_init(&clenv->pstat, TRUE, FALSE);
1133
aa2d1861
MM
1134 if (pq_threadsafe) {
1135 if ((clenv->pgconn = PQconnectdb(CONF.DB_INFO)) == NULL) {
1136 mmsyslog(LOGLEVEL, 0, "PQconnectdb()");
1137 return FALSE;
1138 }
46173fd8 1139 }
46173fd8
MM
1140
1141 return TRUE;
1142}
1143
1144static void
1145clientenv_destructor(pnode_t *pn)
1146{
46173fd8
MM
1147 clientenv *clenv = (clientenv *)pn;
1148
aa2d1861
MM
1149 if (pq_threadsafe) {
1150 if (clenv->pgconn != NULL) {
1151 PQfinish(clenv->pgconn);
1152 clenv->pgconn = NULL;
1153 }
46173fd8 1154 }
46173fd8
MM
1155}
1156
1157
47071c2b
MM
1158/* Allocate and prepare a clenv. Returns NULL on error */
1159static clientenv *
1160alloc_clientenv(void)
1161{
1162 clientenv *clenv;
1163
3dd832e3 1164 pthread_mutex_lock(&clenv_lock);
399db776 1165 clenv = (clientenv *)pool_alloc(&clenv_pool, TRUE);
3dd832e3 1166 pthread_mutex_unlock(&clenv_lock);
47071c2b 1167
aa2d1861
MM
1168 if (!pq_threadsafe) {
1169 if ((clenv->pgconn = PQconnectdb(CONF.DB_INFO)) == NULL) {
1170 syslog(LOG_NOTICE, "alloc_clientenv() - PQconnectdb()");
1171 return free_clientenv(clenv);
1172 }
47071c2b
MM
1173 }
1174
1175 return (clenv);
1176}
1177
1178
1179/* Useful on RSET to reset initial clenv state,
1180 * returns TRUE on success or FALSE on error
1181 */
1182static bool
1183init_clientenv(clientenv *clenv, bool helo)
1184{
5f8db290
MM
1185 if (helo && clenv->helo != NULL)
1186 clenv->helo = mmstrfree(clenv->helo);
1187 if (clenv->from != NULL)
1188 clenv->from = mmstrfree(clenv->from);
47071c2b
MM
1189 empty_rcpts(&clenv->rcpt);
1190
1191 return (TRUE);
1192}
1193
1194
1195/* Frees all ressources allocated by a clenv */
1196static clientenv *
1197free_clientenv(clientenv *clenv)
1198{
5f8db290
MM
1199 if (clenv->helo != NULL)
1200 mmstrfree(clenv->helo);
1201 if (clenv->from != NULL)
1202 mmstrfree(clenv->from);
47071c2b
MM
1203 empty_rcpts(&clenv->rcpt);
1204
aa2d1861
MM
1205 if (!pq_threadsafe) {
1206 if (clenv->pgconn != NULL)
1207 PQfinish(clenv->pgconn);
1208 }
46173fd8 1209
3dd832e3 1210 pthread_mutex_lock(&clenv_lock);
7a56f31f 1211 pool_free((pnode_t *)clenv);
3dd832e3 1212 pthread_mutex_unlock(&clenv_lock);
47071c2b
MM
1213
1214 return (NULL);
1215}
1216
1217
7a56f31f
MM
1218/* Useful to free all rcpts for a clientenv.
1219 * XXX If we used a pool_t per clientenv for these we would simply destroy
1220 */
47071c2b 1221static void
7a56f31f 1222empty_rcpts(list_t *lst)
47071c2b 1223{
5f8db290 1224 node_t *nod, *tmp;
47071c2b 1225
3dd832e3 1226 pthread_mutex_lock(&rcpt_lock);
5f8db290
MM
1227
1228 for (nod = DLIST_TOP(lst); nod != NULL; nod = tmp) {
1229 tmp = DLIST_NEXT(nod);
7a56f31f 1230 pool_free((pnode_t *)nod);
47071c2b 1231 }
5f8db290
MM
1232 DLIST_INIT(lst);
1233
3dd832e3 1234 pthread_mutex_unlock(&rcpt_lock);
47071c2b
MM
1235}
1236
1237
1238/* Checks in the list of aliases for any pattern matching the address, and
1239 * map it to the real address to redirect to, replacing supplied address.
1240 * The addr char array must at least be 64 bytes. Returns FALSE if no alias
1241 * exist for the address, or TRUE on success.
1242 */
1243static bool
46173fd8 1244check_alias(clientenv *clenv, char *addr)
47071c2b 1245{
46173fd8
MM
1246 bool res = FALSE;
1247 char oaddr[64], *user, *domain;
1248 PGresult *pgres;
1249 const char *params[2];
f2c550b1
MM
1250
1251 /* We know that the address is valid, since it has been verified already.
1252 * Copy it to not modify our supplied address, and to keep a backup of the
1253 * user name when performing betch-match operation. Then split the backup
1254 * into user and domain.
1255 */
1256 (void) mm_strcpy(oaddr, addr);
1257 for (user = oaddr, domain = oaddr; *domain != '@'; domain++) ;
1258 *domain++ = '\0';
1259
46173fd8
MM
1260 params[0] = domain;
1261 params[1] = NULL;
1262 if ((pgres = PQexecParams(clenv->pgconn,
1263 "SELECT pattern,box FROM alias WHERE domain=$1", 1, NULL, params, NULL,
1264 NULL, 0)) != NULL) {
1265 int i, t, cur = 0, max = -1;
1266 const char *a = NULL;
1267
1268 for (i = 0, t = PQntuples(pgres); i < t; i++) {
1269 if ((cur = best_match(user, PQgetvalue(pgres, i, 0))) != -1) {
1270 if (cur > max) {
1271 /* Better match, remember this one */
1272 max = cur;
1273 a = PQgetvalue(pgres, i, 1);
f2c550b1 1274 }
47071c2b
MM
1275 }
1276 }
46173fd8
MM
1277 if (max > -1) {
1278 (void) mm_strcpy(addr, a);
1279 res = TRUE;
1280 }
1281 PQclear(pgres);
47071c2b
MM
1282 }
1283
46173fd8 1284 return res;
47071c2b
MM
1285}
1286
1287
c023a59c
MM
1288/* Depending on which is set of <addr> and/or <host>, returns TRUE if any
1289 * of both matched an entry.
1290 */
47071c2b 1291static bool
46173fd8 1292check_nofrom(clientenv *clenv)
47071c2b 1293{
46173fd8
MM
1294 bool res = FALSE;
1295 PGresult *pgres;
47071c2b 1296
46173fd8 1297 if (clenv->c_ipaddr == NULL && clenv->c_hostname == NULL)
5f8db290 1298 return (FALSE);
c023a59c 1299
46173fd8 1300 if ((pgres = PQexec(clenv->pgconn, "SELECT pattern FROM nofrom"))
1757f1a7 1301 != NULL) {
46173fd8
MM
1302 int i, t;
1303 char *pat;
1304
1305 for (i = 0, t = PQntuples(pgres); i < t; i++) {
1306 pat = PQgetvalue(pgres, i, 0);
1307 if (clenv->c_ipaddr != NULL) {
1308 if ((best_match(clenv->c_ipaddr, pat)) != -1) {
1309 res = TRUE;
1310 break;
1311 }
1312 }
1313 if (clenv->c_hostname != NULL) {
1314 if ((best_match(clenv->c_hostname, pat)) != -1) {
1315 res = TRUE;
1316 break;
47071c2b 1317 }
47071c2b
MM
1318 }
1319 }
46173fd8 1320 PQclear(pgres);
47071c2b
MM
1321 }
1322
46173fd8 1323 return res;
47071c2b
MM
1324}
1325
1326
7fd2c069
MM
1327/*
1328 * Used to ensure that only a single server instance with the same
1329 * configuration runs at the same time.
1330 */
1331static int
1332lock_check(const char *path)
1333{
1334 int fd;
1335
1336 if ((fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600)) != -1) {
1337 if (flock(fd, LOCK_EX | LOCK_NB) == 0)
1338 return 0;
1339 (void) close(fd);
1340 }
1341
1342 return -1;
1343}
1344
1345
46cf2cd5
MM
1346/* Returns -1 if <str> does not match <pat> '*' and '?' wildcards pattern.
1347 * Otherwise returns > -1, a value representing the number of literal
1348 * characters in <pat> which exactly matched <str>. This us useful to evaluate
1349 * the best match among a list of patterns.
42dd3825 1350 */
46cf2cd5 1351static int
c023a59c 1352best_match(const char *str, const char *pat)
46cf2cd5
MM
1353{
1354 int lit = 0;
1355
1356 for (; *pat != '*'; pat++, str++) {
e2a16cfa
MM
1357 if (*str == '\0') {
1358 if (*pat != '\0')
1359 return -1;
1360 else
1361 return lit;
47071c2b 1362 }
e2a16cfa
MM
1363 if (*str == *pat)
1364 lit++;
1365 else
1366 if(*pat != '?')
1367 return -1;
47071c2b 1368 }
e2a16cfa
MM
1369 while (pat[1] == '*')
1370 pat++;
47071c2b 1371 do {
46cf2cd5 1372 register int tmp;
47071c2b 1373
e2a16cfa
MM
1374 if ((tmp = best_match(str, pat + 1)) != -1)
1375 return (lit + tmp);
1376 } while (*str++ != '\0');
46cf2cd5 1377
e2a16cfa 1378 return -1;
47071c2b
MM
1379}
1380
1381
1382/* Returns FALSE if this address doesn't exist in our local mailboxes.
46173fd8
MM
1383 * Otherwise it populates boxinfo structure with information about the
1384 * mailbox.
47071c2b
MM
1385 */
1386static bool
46173fd8 1387local_address(clientenv *clenv, struct box_info *boxinfo, const char *address)
47071c2b 1388{
6a796c23 1389 bool res = FALSE;
46173fd8
MM
1390 PGresult *pgres;
1391 const char *params[2];
1392
1393 params[0] = address;
1394 params[1] = NULL;
1395 if ((pgres = PQexecParams(clenv->pgconn,
1396 "SELECT max_size,size,max_msgs,msgs,filter,filter_type FROM box "
1397 "WHERE address=$1", 1, NULL, params, NULL, NULL, 0)) != NULL) {
1398 if (PQntuples(pgres) == 1) {
1399 boxinfo->max_size = atol(PQgetvalue(pgres, 0, 0));
1400 boxinfo->size = atol(PQgetvalue(pgres, 0, 1));
1401 boxinfo->max_msgs = atol(PQgetvalue(pgres, 0, 2));
1402 boxinfo->msgs = atol(PQgetvalue(pgres, 0, 3));
1403 boxinfo->filter = (*(PQgetvalue(pgres, 0, 4)) == 't' ?
1404 TRUE : FALSE);
1405 boxinfo->filter_type = (*(PQgetvalue(pgres, 0, 5)) == 't' ?
1406 TRUE : FALSE);
1407 res = TRUE;
47071c2b 1408 }
46173fd8
MM
1409 PQclear(pgres);
1410 }
47071c2b 1411
6a796c23 1412 return res;
47071c2b
MM
1413}
1414
1415
edc0a306
MM
1416/* Verifies if mailbox <toaddr> filters allow <fromaddr> to post */
1417static bool
46173fd8
MM
1418box_filter_allow(clientenv *clenv, const char *toaddr, const char *fromaddr,
1419 bool filter_type)
edc0a306 1420{
8b13a170 1421 bool res;
46173fd8
MM
1422 PGresult *pgres;
1423 const char *params[2];
edc0a306 1424
8b13a170 1425 /* Default return code depends on filter type */
46173fd8
MM
1426 res = filter_type;
1427
1428 params[0] = toaddr;
1429 params[1] = NULL;
1430 if ((pgres = PQexecParams(clenv->pgconn,
1431 "SELECT pattern FROM filter WHERE address=$1", 1, NULL, params,
1432 NULL, NULL, 0)) != NULL) {
1433 int i, t;
1434
1435 for (i = 0, t = PQntuples(pgres); i < t; i++) {
1436 if (best_match(fromaddr, PQgetvalue(pgres, i, 0)) != -1) {
1437 res = !res;
1438 break;
edc0a306 1439 }
46173fd8
MM
1440 }
1441 PQclear(pgres);
1442 }
edc0a306
MM
1443
1444 return res;
1445}
1446
1447
c363ee92 1448/* Fills str which should be at least 32 bytes in length with current time */
47071c2b
MM
1449static void
1450rfc_time(char *str)
1451{
1452 /* Thu, 07 Dec 2000 07:36:15 -0000 */
c363ee92 1453 const static char *days[] = {
47071c2b
MM
1454 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
1455 };
c363ee92 1456 const static char *months[] = {
47071c2b
MM
1457 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1458 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1459 };
1460 time_t secs;
1461 struct tm *gtim;
1462
47071c2b
MM
1463 secs = time(NULL);
1464 gtim = gmtime(&secs);
1465
1466 snprintf(str, 32, "%s, %02d %s %04d %02d:%02d:%02d -0000",
5f8db290
MM
1467 days[gtim->tm_wday], gtim->tm_mday, months[gtim->tm_mon],
1468 gtim->tm_year + 1900, gtim->tm_hour, gtim->tm_min,
1469 gtim->tm_sec);
47071c2b
MM
1470}
1471
1472
1473/* Returns whether or not supplied address is valid, and if it is return the
a4da92fc
MM
1474 * parsed address in the supplied string. String should be at least <len>
1475 * bytes. <clenv> can only be NULL if HOST_NORES is used for <res>.
47071c2b
MM
1476 */
1477static bool
a4da92fc 1478valid_address(clientenv *clenv, char *to, size_t len, char *addr, int res)
47071c2b 1479{
c363ee92 1480 char *ptr, *a, *h;
47071c2b
MM
1481
1482 mm_strlower(addr);
1483
1484 /* First locate required @ */
e2a16cfa 1485 for (ptr = addr; *ptr != '\0' && *ptr != '@'; ptr++) ;
611589c7
MM
1486 if (*ptr == '\0')
1487 return (FALSE);
c363ee92 1488 h = ptr + 1;
47071c2b 1489 /* Then scan to the left */
82396359 1490 for (ptr--; ptr >= addr && VALID_ADDR_CHAR(*ptr); ptr--) ;
611589c7
MM
1491 if (h - ptr < 3 || ptr < addr)
1492 return (FALSE);
c363ee92
MM
1493 a = ++ptr;
1494 /* Now to the right */
82396359 1495 for (ptr = h; *ptr != '\0' && VALID_ADDR_CHAR(*ptr); ptr++) ;
611589c7
MM
1496 if (ptr - h < 2)
1497 return (FALSE);
c363ee92 1498 *ptr = '\0';
47071c2b 1499 /* Now validate hostname part */
f2c550b1 1500 if (valid_host(clenv, h, res, FALSE, TRUE)) {
a4da92fc 1501 mm_strncpy(to, a, len - 1);
47071c2b
MM
1502 return (TRUE);
1503 }
1504
1505 return (FALSE);
1506}
1507
1508
1509static bool
f2c550b1 1510valid_host(clientenv *clenv, char *host, int res, bool addr, bool sanity)
47071c2b 1511{
47071c2b 1512
5f8db290 1513 if (addr && res != HOST_RES_MX && valid_ipaddress(host))
f2c550b1 1514 return TRUE;
47071c2b 1515
f2c550b1
MM
1516 if (sanity) {
1517 register char *ptr;
47071c2b 1518
f2c550b1
MM
1519 mm_strlower(host);
1520
1521 /* First make sure all characters are valid */
1522 for (ptr = host; *ptr != '\0'; ptr++)
a4da92fc 1523 if (!VALID_HOST_CHAR(*ptr))
f2c550b1
MM
1524 return FALSE;
1525
1526 /* Now verify that all parts of the hostname are starting with
1527 * an alphanumeric char
1528 */
1529 ptr = host;
1530 while (*ptr != '\0') {
3dd832e3 1531 if (!isalnum((int)*ptr))
f2c550b1
MM
1532 return FALSE;
1533 /* Find next host part */
1534 while (*ptr != '\0' && *ptr != '.') ptr++;
1535 if (*ptr == '.') {
1536 ptr++;
1537 continue;
1538 }
1539 if (*ptr == '\0') break;
ec182166 1540 ptr++;
47071c2b 1541 }
47071c2b
MM
1542 }
1543
917e9cbb
MM
1544 /* Hostname seems valid, last sanity checking test consists of optional
1545 * resolving
1546 */
1547 if (res != HOST_NORES) {
46cf2cd5
MM
1548 char answer[64];
1549
917e9cbb
MM
1550 if (res == HOST_RES_MX) {
1551 /* Check for an MX DNS IP address entry for it */
1552 if ((a_res_query(clenv, host, C_IN, T_MX, answer,
1553 sizeof(answer) - 1)) == -1)
f2c550b1 1554 return FALSE;
917e9cbb
MM
1555 } else if (res == HOST_RES) {
1556 /* Check if hostname resolves to normal A record */
1557 if ((a_res_query(clenv, host, C_IN, T_A, answer,
1558 sizeof(answer) - 1)) == -1)
f2c550b1 1559 return FALSE;
917e9cbb 1560 }
47071c2b
MM
1561 }
1562
f2c550b1 1563 return TRUE;
47071c2b
MM
1564}
1565
1566
46cf2cd5 1567/* Some more parsing magic for IP address sanity checking */
ec182166
MM
1568static bool
1569valid_ipaddress(const char *addr)
1570{
46cf2cd5 1571 char unit[5], *uptr, *utptr;
ec182166
MM
1572 int units;
1573
46cf2cd5 1574 for (units = 0, uptr = unit, utptr = unit + 4; uptr < utptr; addr++) {
e2a16cfa 1575 if (*addr == '\0' || *addr == '.') {
ec182166
MM
1576 if (uptr > unit && units < 4) {
1577 register int n;
1578
1579 *uptr = '\0';
1580 n = atoi(unit);
611589c7
MM
1581 if (n < 0 || n > 255)
1582 break;
ec182166
MM
1583 uptr = unit;
1584 units++;
1585 } else return (FALSE);
611589c7
MM
1586 if (*addr == '\0')
1587 break;
3dd832e3 1588 } else if (isdigit((int)*addr))
611589c7
MM
1589 *uptr++ = *addr;
1590 else
1591 return (FALSE);
46cf2cd5 1592 }
611589c7
MM
1593 if (!(units == 4 && *addr == '\0'))
1594 return (FALSE);
ec182166
MM
1595
1596 return (TRUE);
1597}
1598
1599
1bb24f38 1600/* This function is called for every line read from the message */
47071c2b
MM
1601static int
1602validate_msg_line(char *line, ssize_t *len, int *res, void *udata)
1603{
1bb24f38
MM
1604 register struct validate_udata *ud = udata;
1605 int eres = FDBRB_OK;
47071c2b 1606
1bb24f38
MM
1607 /* Verify for message termination indicator, a single '.', which is both
1608 * valid in headers or body. If we're still in headers, we ensure to
1609 * create missing headers and append the end of headers empty line, to
1610 * avoid broken messages.
1611 */
1612 if (*len == 1 && *line == '.') {
1613 if (ud->header) {
1614 *line = '\0';
1615 *len = 0;
1616 eres = FDBRB_ADDSTOP;
1617 goto endheader;
1618 } else
1619 return FDBRB_STOP;
1620 }
1621
1622 if (ud->header) {
1623
1624 /* Still reading header and expecting ones */
1625 char *ptr;
1626
1627 /* Empty line means that body will follow */
1628 if (*len == 0)
1629 goto endheader;
1630
1631 /* Ensure that entry seems a valid message header or also stop.
b333f2df
MM
1632 * Basically, make sure that a header only comports alphanumeric
1633 * characters and dashes in the name, and that the name follows by
1634 * ': '. Also allow continueing header lines which begin with
1635 * spaces/tabs.
1bb24f38 1636 */
de9989f3 1637 if (*line != '\t' && *line != ' ') {
3dd832e3
MM
1638 for (ptr = line; *ptr != '\0' && (isalnum((int)*ptr) ||
1639 *ptr == '-');
de9989f3
MM
1640 ptr++) ;
1641 if (*ptr != ':')
1642 goto endheader;
de9989f3 1643 }
1bb24f38
MM
1644
1645 /* Count number of Received: headers (SMTP hops) */
1646 if (mm_strncmp(line, "Received:", 9) == 0) {
47071c2b
MM
1647 ud->hops++;
1648 if (ud->hops > CONF.MAX_HOPS) {
1bb24f38 1649 /* Exceeded allowed number of hops, cancel reception */
47071c2b 1650 *res = CFDBRB_HOPS;
1bb24f38
MM
1651 return FDBRB_STOP;
1652 }
1653 return FDBRB_OK;
47071c2b 1654 }
47071c2b 1655
1bb24f38
MM
1656 /* Now verify for existance of headers we consider mandatory.
1657 * We'll create them if necessary.
1658 */
92bee4b2
MM
1659 /*
1660 * This one seems to not strictly follow RFC example as for case
1661 * sensitivity, I often see Message-ID: and Message-id:, which caused
1662 * mmmail to add an additional Message-Id: line if not doing this
1663 * check case insensitively.
1664 */
1665 if (mm_strncasecmp(line, "Message-Id:", 11) == 0)
1bb24f38
MM
1666 ud->msgid = TRUE;
1667 else if (mm_strncmp(line, "Date:", 5) == 0)
1668 ud->date = TRUE;
1669 else if (mm_strncmp(line, "From:", 5) == 0)
1670 ud->from = TRUE;
1671 else if (mm_strncmp(line, "To:", 3) == 0)
1672 ud->to = TRUE;
1673
1674 return FDBRB_OK;
1675
1676 } else {
1677
1678 /* Reading message body */
1679
856ca38a
MM
1680 /* ".." lines must be converted to "." ones */
1681 if (*len == 2 && line[0] == '.' && line[1] == '.') {
1682 line[1] = '\0';
47071c2b
MM
1683 (*len)--;
1684 }
1bb24f38
MM
1685
1686 return FDBRB_OK;
1687
47071c2b
MM
1688 }
1689
1bb24f38
MM
1690endheader:
1691
1692 /* We reached end of headers */
1693 ud->header = FALSE;
1694
1695 {
1696 char tline[1024], tdata[32];
1697
1698 /* Create the headers we consider mendatory if they were not supplied.
1699 * We append them after all headers that were supplied, this way the
1700 * Received: lines are guaranteed to be first. Note that this is only
1701 * safe if the total expansion we cause does not exceed 1024 bytes,
1702 * which buffer is guarenteed to have been reserved for a message line
1703 * by mmfd(3)'s fdbreadbuf(). Our additionnal expansion will never
1704 * exceed 320 bytes in this case.
1705 */
1706 *tline = '\0';
1707 if (!ud->msgid) {
1708 iso_time(tdata);
1709 (void) snprintf(tline, 1023, "Message-Id: <%s.%08lX-%lu@%s>\r\n",
1710 tdata, ud->clenv->id, ud->clenv->messages,
1711 ud->clenv->iface->hostname);
1712 }
1713 if (!ud->date) {
1714 rfc_time(tdata);
1715 (void) snprintf(tline, 1023, "%sDate: %s\r\n",
1716 tline, tdata);
1717 }
1718 if (!ud->from)
1719 (void) snprintf(tline, 1023, "%sFrom: %s\r\n",
1720 tline, ud->clenv->from);
1721 if (!ud->to)
1722 (void) snprintf(tline, 1023, "%sTo: undisclosed-recipients:;\r\n",
1723 tline);
1724
1725 if (*len == 0) {
1726 /* Valid end of header, an empty line. If no headers to add, all
1727 * is good. Otherwise, we must simply replace the current line by
1728 * the headers plus an empty line. Because the headers already
1729 * contain a newline, we just copy it over the current line,
1730 * fdbreadbuf() will append an additional one automatically.
1731 */
1732 if (*tline != '\0')
1733 *len = (mm_strcpy(line, tline) - line);
1734 } else {
1735 /* Invalid end of header, we must insert our headers, if any,
1736 * before the current line, along with an empty line.
1737 * Unfortunately, this could discard some bytes at the end of the
1738 * invalid last header line (the first body line) if it was too
1739 * long. However, this was a malformed message anyways and needed
1740 * major fixing. We could have errored instead if we were strict.
1741 */
1742 (void) mm_strncat(tline, "\r\n", 1023);
1743 (void) mm_strncat(tline, line, 1023);
1744 *len = (mm_strcpy(line, tline) - line);
1745 }
1746 }
1747
1748 return eres;
47071c2b
MM
1749}
1750
1751
1752/* This function is called by STATE_DATA and permits the client to send
1753 * the message data, respecting expected limits. Returns FALSE if the state
1bb24f38 1754 * should switch to STATE_ERROR, on fatal error (i.e. out of memory)
47071c2b
MM
1755 */
1756static bool
1757do_data(clientenv *clenv)
1758{
47071c2b
MM
1759 struct fdbrb_buffer *fdbrb;
1760 int res, err = DATA_INTERNAL;
1761 bool ok = FALSE;
47071c2b
MM
1762 struct validate_udata ud;
1763
1764 reply(clenv->fdb, data_msg[DATA_SUBMIT].code, FALSE,
1765 data_msg[DATA_SUBMIT].msg);
1766 fdbflushw(clenv->fdb);
1767
1768 /* Call our famous fdbreadbuf() which will read lines in a single buffer
1769 * and validate them via the validate_msg_line() function (above).
1770 * We restict the maximum length of a single line to 1024 characters
1771 * and are starting with an initial buffer of 32K, buffer which will
1772 * double in size whenever required. Of course don't read more than
1773 * CONF.MAX_DATA_SIZE bytes or CONF.MAX_DATA_LINES lines.
1774 * See mmfd(3) man page for details, and mmlib/mmfd.c
1775 */
1bb24f38
MM
1776 ud.hops = 0;
1777 ud.msgid = ud.date = ud.from = ud.to = FALSE;
1778 ud.header = TRUE;
1779 ud.clenv = clenv;
47071c2b
MM
1780 res = fdbreadbuf(&fdbrb, clenv->fdb, 32768, 1024, CONF.MAX_DATA_SIZE,
1781 CONF.MAX_DATA_LINES, validate_msg_line, &ud, FALSE);
1bb24f38 1782
47071c2b
MM
1783 /* Map results to DATA suitable ones */
1784 switch (res) {
1785 case FDBRB_MEM:
1786 mmsyslog(0, LOGLEVEL, "%08X * Out of memory", clenv->id);
1787 err = DATA_INTERNAL;
e334174e 1788 REGISTER_ERROR(clenv);
47071c2b
MM
1789 break;
1790 case FDBRB_OVERFLOW:
1791 mmsyslog(0, LOGLEVEL, "%08X * Message size too large", clenv->id);
1792 err = DATA_OVERFLOW;
e334174e 1793 REGISTER_ERROR(clenv);
47071c2b
MM
1794 break;
1795 case FDBRB_TIMEOUT:
1796 mmsyslog(0, LOGLEVEL, "%08X * Input timeout", clenv->id);
1797 if (CONF.STATFAIL_TIMEOUT)
8303aa4f 1798 mmstat(&clenv->pstat, STAT_UPDATE, 1, "mmsmtpd|failed|timeout|%s",
47071c2b
MM
1799 clenv->c_ipaddr);
1800 break;
1801 case FDBRB_EOF:
1802 mmsyslog(0, LOGLEVEL, "%08X * Unexpected EOF", clenv->id);
1803 break;
1804 case CFDBRB_HOPS:
1805 mmsyslog(0, LOGLEVEL, "%08X * Too many hops", clenv->id);
1806 err = DATA_HOPS;
e334174e 1807 REGISTER_ERROR(clenv);
47071c2b
MM
1808 break;
1809 case FDBRB_OK:
1810 ok = TRUE;
1811 break;
1812 }
1813
46173fd8 1814 if (ok)
5f8db290
MM
1815 ok = do_data_file(clenv, fdbrb);
1816
5f8db290
MM
1817 fdbfreebuf(&fdbrb); /* Internally only frees if not already freed */
1818 if (ok)
1819 err = DATA_OK;
47071c2b
MM
1820 reply(clenv->fdb, data_msg[err].code, FALSE, data_msg[err].msg);
1821
1822 /* Reset mail state (and free RCPTs) */
1823 init_clientenv(clenv, FALSE);
1824
1825 return (ok);
1826}
1827
5f8db290 1828/* Create a Received: line, isolated to prevent code duplication among
193955a0 1829 * different storage methods. Returns length of received line in bytes.
5f8db290 1830 */
193955a0 1831inline static size_t
5f8db290
MM
1832do_data_received(char *line, size_t len, clientenv *clenv, rcptnode *rnode,
1833 const char *smtptime)
1834{
1bb24f38 1835 (void) snprintf(line, len - 1,
5f8db290
MM
1836 "Received: from %s ([%s] HELO=%s)\r\n\tby %s (%s) "
1837 "with SMTP\r\n\tid %08lX-%lu for <%s>;\r\n\t%s\r\n",
1bb24f38 1838 (clenv->c_hostname ? clenv->c_hostname : "(unresolved)"),
5f8db290 1839 clenv->c_ipaddr,
1bb24f38 1840 (clenv->helo ? clenv->helo : "(unidentified)"),
5f8db290
MM
1841 clenv->iface->hostname, DAEMON_VERSION, clenv->id,
1842 clenv->messages, rnode->foraddress, smtptime);
193955a0
MM
1843
1844 return mm_strlen(line);
5f8db290
MM
1845}
1846
5f8db290
MM
1847
1848/* Record statistics using mmstat(3) facility, called by do_data to prevent
1849 * code duplication among different storage methods.
1850 */
1851static void
1852do_data_stats(clientenv *clenv, rcptnode *rnode, size_t len)
1853{
1854 char *domptr;
1855
1856 mmstat_transact(&clenv->pstat, TRUE);
1857
1858 /* Record per-box statistics. Note that when aliases are used, the actual
1859 * target mailbox is used.
1860 */
1861 mmstat(&clenv->pstat, STAT_UPDATE, 1, "mmmail|box|%s|messages-in",
1862 rnode->address);
1863 mmstat(&clenv->pstat, STAT_UPDATE, len, "mmmail|box|%s|bytes-in",
1864 rnode->address);
1865
1866 /* And per-domain ones. The address was previously validated successfully
1867 * and the '@' character is guarenteed to be present for mm_strchr().
1868 */
1869 domptr = mm_strchr(rnode->address, '@');
1870 domptr++;
1871 mmstat(&clenv->pstat, STAT_UPDATE, 1, "mmmail|domain|%s|messages-in",
1872 domptr);
1873 mmstat(&clenv->pstat, STAT_UPDATE, len, "mmmail|domain|%s|bytes-in",
1874 domptr);
1875
1876 mmstat_transact(&clenv->pstat, FALSE);
1877}
1878
1879
193955a0
MM
1880/* Returns TRUE if the client address/hostname is allowed to relay messages
1881 * for non-local addresses, or FALSE otherwise, with reason set to either
1882 * RCPT_UNKNOWN (destination address on a locally handled domain but
1883 * unexisting) or RCPT_RELAY (relay denied for sender address/hostname).
1884 * If TRUE is returned, the post can be relayed, since it does not belong to
1885 * any local domains we are handling and that the client has relaying rights.
1886 */
1887bool
1888address_relay_allow(clientenv *clenv, int *reason, const char *addr)
5f8db290 1889{
193955a0 1890 bool res = TRUE;
193955a0 1891 const char *domain;
46173fd8 1892 PGresult *pgres;
5f8db290 1893
193955a0 1894 /* Is address to a local domain but unknown to us? */
5f8db290 1895
193955a0
MM
1896 /* We know that the supplied address is valid, it thus must have '@'.
1897 * Set domain pointer to start of domain name.
1898 */
1899 for (domain = addr; *domain != '@'; domain++) ;
1900 domain++;
46173fd8 1901
193955a0 1902 /* Query database entries and search for any matching pattern. */
46173fd8
MM
1903 if ((pgres = PQexec(clenv->pgconn, "SELECT pattern FROM relaylocal"))
1904 != NULL) {
1905 int i, t;
5f8db290 1906
46173fd8
MM
1907 for (i = 0, t = PQntuples(pgres); i < t; i++) {
1908 if (best_match(domain, PQgetvalue(pgres, i, 0)) != -1) {
1909 res = FALSE;
1910 *reason = RCPT_UNKNOWN;
1911 break;
1912 }
1913 }
1914 PQclear(pgres);
1915 }
1916
193955a0
MM
1917 /* Return with error immediately if address is locally handled */
1918 if (!res)
1919 return res;
5f8db290 1920
193955a0
MM
1921 /* No, so it appears that it would need relaying. Is the client then
1922 * allowed to relay messages through us? Verify via the client's IP
1923 * address and/or hostname.
1924 */
193955a0 1925 res = FALSE;
5f8db290 1926
46173fd8
MM
1927 if ((pgres = PQexec(clenv->pgconn, "SELECT pattern FROM relayfrom"))
1928 != NULL) {
1929 int i, t;
1930 char *pat;
1931
1932 for (i = 0, t = PQntuples(pgres); i < t; i++) {
1933 pat = PQgetvalue(pgres, i, 0);
1934 if (clenv->c_ipaddr != NULL) {
1935 if (best_match(clenv->c_ipaddr, pat) != -1) {
1936 res = TRUE;
1937 break;
193955a0 1938 }
5f8db290 1939 }
46173fd8
MM
1940 if (clenv->c_hostname != NULL) {
1941 if (best_match(clenv->c_hostname, pat) != -1) {
1942 res = TRUE;
1943 break;
1944 }
1945 }
1946 }
1947 PQclear(pgres);
1948 }
5f8db290 1949
193955a0
MM
1950 if (!res)
1951 *reason = RCPT_RELAY;
5f8db290 1952
193955a0
MM
1953 return res;
1954}
5f8db290 1955
193955a0
MM
1956/* Produces time in the format yyyymmddhhmmss. Supplied string must at least
1957 * be 15 bytes (16 recommended).
1958 */
1959static void
1960iso_time(char *str)
1961{
1962 time_t secs;
1963 struct tm *gtim;
1964
1965 secs = time(NULL);
1966 gtim = gmtime(&secs);
1967
1968 (void) snprintf(str, 16, "%04d%02d%02d%02d%02d%02d",
1969 gtim->tm_year + 1900, gtim->tm_mon + 1, gtim->tm_mday,
1970 gtim->tm_hour, gtim->tm_min, gtim->tm_sec);
1971}
1972
1973/* Saves a message to disk, into the <box> directory, creating the directory
1974 * if needed. It ensures to create a unique filename in that directory. The
1975 * directory and the file will be located into MAIL_DIR. Locks should be held
46173fd8 1976 * as necessary before calling this function as atomic behavior is required
193955a0
MM
1977 * among other tasks. <recvline> consists of the "Received:" header which will
1978 * be written first, followed by the data held in the <fdbrb> buffer. The
1979 * fullpath to the created filename will be stored into supplied <path>, which
978cad00 1980 * must be at least 256 bytes.
193955a0
MM
1981 */
1982static bool
1983message_write(char *path, const char *recvline, size_t recvlen,
1984 struct fdbrb_buffer *fdbrb, const char *box)
1985{
1986 bool ok = FALSE;
1987 char filetime[16];
1988 int i, fd;
46173fd8 1989 u_int32_t r;
193955a0
MM
1990
1991 fd = -1;
1992
1993 /* Make sure that directory exists, performing an mkdir(2) which will
1994 * fail if it already does.
1995 */
978cad00 1996 (void) snprintf(path, 255, "%s/%s", CONF.MAIL_DIR, box);
193955a0
MM
1997 if (mkdir(path, 00750) == -1 && errno != EEXIST) {
1998 mmsyslog(0, LOGLEVEL, "mkdir(%s) == %s", path, strerror(errno));
1999 return FALSE;
2000 }
2001
2002 /* Generate unique filename to store the message within the mail
2003 * directory. We will make 64 retries maximum in an attempt to ensure
2004 * creation of a unique filename.
2005 */
2006 iso_time(filetime);
2007 for (i = 0; i < 64; i++) {
46173fd8 2008 r = (u_int32_t)random();
978cad00 2009 (void) snprintf(path, 255, "%s/%s/%s.%08X", CONF.MAIL_DIR,
46173fd8 2010 box, filetime, r);
193955a0
MM
2011 if ((fd = open(path, O_CREAT | O_EXCL | O_WRONLY, 00640)) != -1)
2012 break;
2013 }
46173fd8
MM
2014 /* Write final relative path in supplied buffer */
2015 (void) snprintf(path, 255, "%s/%s.%08X", box, filetime, r);
193955a0
MM
2016
2017 /* Successfully created a new unique file, save message data.
2018 * We also verify the return value of close(2), but are not calling
2019 * fdatasync(2) or fsync(2) which would considerably impact
2020 * performance.
2021 */
2022 if (fd != -1) {
2023 if (write(fd, recvline, recvlen) == recvlen &&
2024 write(fd, fdbrb->array, fdbrb->current) == fdbrb->current
2025 && close(fd) == 0)
2026 ok = TRUE;
2027 else {
2028 mmsyslog(0, LOGLEVEL, "write()/close()|(%s) == %s",
2029 path, strerror(errno));
2030 (void) close(fd);
46173fd8
MM
2031 (void) snprintf(path, 255, "%s/%s/%s.%08X", CONF.MAIL_DIR, box,
2032 filetime, r);
193955a0 2033 (void) unlink(path);
5f8db290 2034 }
193955a0
MM
2035 } else
2036 mmsyslog(0, LOGLEVEL, "open(%s) == %s", path, strerror(errno));
5f8db290 2037
46173fd8
MM
2038 if (!ok)
2039 *path = '\0';
2040
193955a0
MM
2041 return ok;
2042}
2043
2044/* For each RCPT, queue the message appropriately */
2045static bool
2046do_data_file(clientenv *clenv, struct fdbrb_buffer *fdbrb)
2047{
2048 char smtptime[32], recvline[1024];
2049 rcptnode *rnode;
2050 size_t recvlen;
2051 bool ok;
2052
2053 ok = TRUE;
2054 rfc_time(smtptime);
5f8db290 2055
193955a0
MM
2056 DLIST_FOREACH(&clenv->rcpt, rnode) {
2057 /* Create Received: line */
2058 recvlen = do_data_received(recvline, 1024, clenv, rnode, smtptime);
2059 /* Queue for relaying or into the mailbox if local */
2060 if (!rnode->relay)
2061 ok = do_data_queue_box(clenv, recvline, recvlen, fdbrb, rnode);
20e5cbf1
MM
2062 else {
2063 if (!CONF.RELAYING)
2064 ok = FALSE;
2065 else
2066 ok = do_data_queue_relay(clenv, recvline, recvlen, fdbrb,
2067 rnode);
2068 }
5f8db290
MM
2069 if (!ok)
2070 break;
193955a0
MM
2071 }
2072
2073 return ok;
2074}
2075
2076/* Queue a message to a local mailbox */
2077static bool
2078do_data_queue_box(clientenv *clenv, const char *recvline, size_t recvlen,
2079 struct fdbrb_buffer *fdbrb, rcptnode *rnode)
2080{
46173fd8
MM
2081 char path[256], v[16];
2082 PGresult *pgres;
2083 const char *params[4];
193955a0
MM
2084
2085 /* Obtain global lock. This ensures that both file and database data
2086 * are in sync and between both mmsmtpd and mmpop3d. Moreover, it even
2087 * allows proper serialization of operations over NFS.
46173fd8
MM
2088 * XXX We don't use this type of locking anymore for now. A file advisory
2089 * lock might be wanted though, or a PostgreSQL advisory lock.
193955a0 2090 */
5f8db290 2091
46173fd8
MM
2092 if (!message_write(path, recvline, recvlen, fdbrb, rnode->address))
2093 return FALSE;
193955a0 2094
46173fd8
MM
2095 (void) snprintf(v, 15, "%ld", (long)fdbrb->current + recvlen);
2096 params[0] = rnode->address;
2097 params[1] = v;
2098 params[2] = path;
2099 params[3] = NULL;
2100 if ((pgres = PQexecParams(clenv->pgconn,
2101 "INSERT INTO mail (box,size,file) VALUES($1,$2,$3)", 3, NULL,
2102 params, NULL, NULL, 0)) != NULL)
2103 PQclear(pgres);
2104 else {
2105 syslog(LOG_NOTICE, "do_date_queue_box() - PQexecParams()");
2106 (void) unlink(path);
193955a0 2107
46173fd8
MM
2108 return FALSE;
2109 }
193955a0 2110
46173fd8
MM
2111 do_data_stats(clenv, rnode, fdbrb->current + recvlen);
2112
2113 return TRUE;
193955a0
MM
2114}
2115
2116/* Queue a message for relaying */
2117static bool
2118do_data_queue_relay(clientenv *clenv, const char *recvline, size_t recvlen,
2119 struct fdbrb_buffer *fdbrb, rcptnode *rnode)
2120{
46173fd8 2121 char path[256], *user, *domain, *restore, v[16];
193955a0 2122 bool ok = TRUE;
46173fd8
MM
2123 PGresult *pgres;
2124 const char *params[7];
193955a0
MM
2125
2126 /* This lock allows to maintain atomicity between the message file and
2127 * its corresponding database entry, between mmsmtpd(8) and mmrelayd(8).
46173fd8 2128 * XXX We no longer currently use a lock.
193955a0 2129 */
193955a0 2130
f2c550b1
MM
2131 /* We know that the address is valid in the rcpt node, separate it into
2132 * user and domain strings.
2133 */
2134 for (restore = rnode->address; *restore != '@'; restore++) ;
2135 *restore = '\0';
2136 user = rnode->address;
2137 domain = &restore[1];
2138
193955a0
MM
2139 if (message_write(path, recvline, recvlen, fdbrb, "relayqueue")) {
2140 /* Message file saved successfully, add corresponding DB entry */
46173fd8
MM
2141 (void) snprintf(v, 15, "%ld", (long)fdbrb->current + recvlen);
2142 params[0] = clenv->from;
2143 params[1] = clenv->c_ipaddr;
2144 params[2] = domain;
2145 params[3] = user;
2146 params[4] = v;
2147 params[5] = path;
2148 params[6] = NULL;
2149 if ((pgres = PQexecParams(clenv->pgconn, "INSERT INTO relayqueue "
2150 "(\"from\",ipaddr,todomain,touser,size,file) "
2151 "VALUES($1,$2,$3,$4,$5,$6)", 6, NULL, params, NULL, NULL, 0))
2152 != NULL)
2153 PQclear(pgres);
2154 else {
2155 syslog(LOG_NOTICE, "do_data_queue_relay() - PQexecParams()");
2156 (void) snprintf(path, 255, "%s/%s", CONF.MAIL_DIR, path);
193955a0
MM
2157 (void) unlink(path);
2158 ok = FALSE;
2159 }
2160 } else
2161 ok = FALSE;
2162
f2c550b1
MM
2163 /* Restore string to original value */
2164 *restore = '@';
2165
12cbd451
MM
2166 /*
2167 * We now want to notify mmrelayd that it should verify for ready to
d6eecfe4
MM
2168 * relay mail as soon as possible instead of waiting until its next
2169 * scheduled round.
12cbd451 2170 */
46173fd8
MM
2171 if (ok)
2172 do_data_queue_notify(clenv);
12cbd451 2173
03d85e08 2174 return ok;
5f8db290
MM
2175}
2176
d6eecfe4
MM
2177/*
2178 * Attempt to notify mmrelayd(8) that at least one message is ready in the
2179 * queue to route.
20e5cbf1 2180 * XXX Broken! Do not use RELAYING = TRUE yet!
d6eecfe4
MM
2181 */
2182static void
20e5cbf1 2183do_data_queue_notify(clientenv *clenv)
d6eecfe4 2184{
20e5cbf1
MM
2185 bool ok = FALSE;
2186 /*
2187 notify_msg_t msg;
2188 */
2189
2190 /* XXX
2191 * We now actually require the lock, since we need to send exactly one
2192 * message per new queued mail. Fix accordingly. We however can fill in
2193 * the data for the message before sending it to hold the lock for as
2194 * short as possible. We also possibly only need it to verify if we're
2195 * connected, or to mark the socket disconnected... Since we only need
2196 * an atomic send per notification?
2197 */
d6eecfe4
MM
2198
2199 /*
2200 * If we cannot obtain lock, we know that it's already being notified, and
2201 * we don't need to do anything.
2202 */
3dd832e3 2203 if (pthread_mutex_lock(&relayd_lock) != 0)
d6eecfe4
MM
2204 return;
2205
2206 /*
2207 * If socket wasn't open yet, attempt to open it. If we cannot, we have
2208 * nothing to notify, since the relay daemon most probably doesn't run.
2209 */
2210 if (relayd_sock == -1) {
2211 if ((relayd_sock = do_data_queue_notify_connect()) == -1)
2212 goto end;
2213 }
2214
2215 /*
2216 * Send a notification packet. If we cannot send it, attempt to reconnect
2217 * and send it again, but once only.
2218 */
2219 for (;;) {
3dd832e3 2220 if (write(relayd_sock, "N", 1) != 1) {
d6eecfe4
MM
2221 (void) close(relayd_sock);
2222 if ((relayd_sock = do_data_queue_notify_connect()) != -1)
2223 continue;
2224 } else
2225 ok = TRUE;
2226 break;
2227 }
2228
2229end:
2230
2231 if (!ok)
2232 mmsyslog(0, LOGLEVEL,
2233 "mmrelayd(8) could not be notified (not running?)");
2234
3dd832e3 2235 (void) pthread_mutex_unlock(&relayd_lock);
d6eecfe4
MM
2236}
2237
2238/*
2239 * Attempt to open the mmrelayd(8) notification socket, returning the
2240 * filedescriptor on success, or -1 on failure.
2241 */
2242static int
2243do_data_queue_notify_connect(void)
2244{
20e5cbf1 2245 int fd, opt;
d6eecfe4
MM
2246 struct sockaddr_un addr;
2247
2248 if ((fd = socket(AF_LOCAL, SOCK_DGRAM, 0)) != -1) {
20e5cbf1
MM
2249 opt = (int)BALIGN_CEIL(sizeof(notify_msg_t), 1024);
2250 if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(int)) == -1)
2251 mmsyslog(0, LOGLEVEL,
2252 "do_data_queue_notify_connect() - setsockopt() - (%s)",
2253 strerror(errno));
d6eecfe4 2254 mm_memclr(&addr, sizeof(struct sockaddr_un));
20e5cbf1 2255 (void) mm_strncpy(addr.sun_path, CONF.MMRELAYD_SOCKET_PATH, 100);
d6eecfe4 2256 addr.sun_family = AF_UNIX;
3dd832e3 2257 if ((connect(fd, (struct sockaddr *)&addr,
d6eecfe4
MM
2258 sizeof(struct sockaddr_un))) == -1) {
2259 (void) close(fd);
2260 fd = -1;
2261 } else
2262 mmsyslog(0, LOGLEVEL, "Cannot connect to mmrelayd(8) socket "
2263 "'%s' (%s)", addr.sun_path, strerror(errno));
2264 } else
2265 mmsyslog(0, LOGLEVEL, "Cannot create socket descriptor (%s)",
2266 strerror(errno));
2267
2268 return fd;
2269}
2270
47071c2b
MM
2271
2272/* This is the main function that is called to serve a client.
2273 * It comports the main loop and state switcher.
2274 */
2275static int
2276handleclient(unsigned long id, int fd, clientlistnode *clientlnode,
2277 struct iface *iface, struct async_clenv *aclenv)
2278{
2279 char buffer[1024], ipaddr[20], *tmp;
2280 int len, state, nstate, timeout, dstatus;
2281 clientenv *clenv;
2282 struct sockaddr_in *sinaddr;
2283 fdbuf *fdb;
2284 int64_t data_in, data_out;
2285 unsigned long rcpt_in, messages_in, time_total;
2286 time_t time_start, time_end;
47071c2b
MM
2287
2288 data_in = data_out = rcpt_in = messages_in = 0;
2289 dstatus = MMS_RESOURCE_ERROR;
2290 timeout = clientlnode->timeout;
2291 clenv = NULL;
2292
2293 /* Obtain IP address of client */
2294 sinaddr = (struct sockaddr_in *)&clientlnode->client;
2295 if ((tmp = inet_ntoa(sinaddr->sin_addr))) mm_strncpy(ipaddr, tmp, 19);
2296 else mm_strncpy(ipaddr, "0.0.0.0", 8);
2297
5f8db290 2298 if (clientlnode->hostname != NULL)
47071c2b
MM
2299 /* Log user's address and hostname */
2300 mmsyslog(1, LOGLEVEL, "%08X Connect: [%s] - (%s)", id, ipaddr,
2301 clientlnode->hostname);
2302 else
2303 /* Log user's address only */
2304 mmsyslog(1, LOGLEVEL, "%08X Connect: [%s]", id, ipaddr);
2305
2306 time_start = time(NULL);
2307
5eb34fba 2308 if ((fdb = fdbopen(&gfdf, &fdbc, fd, 8192, 8192, CONF.BANDWIDTH_IN * 1024,
5f8db290
MM
2309 CONF.BANDWIDTH_OUT * 1024, timeout, timeout, FALSE))
2310 != NULL) {
47071c2b
MM
2311
2312 /* Allocate our clientenv to share with state functions */
5f8db290 2313 if ((clenv = alloc_clientenv()) != NULL) {
47071c2b
MM
2314
2315 /* Set some configuration options such as max_rcpts,
2316 * max_mesg_lines, max_mesg_size, hostname...
2317 */
2318 clenv->fdb = fdb;
2319 clenv->buffer = buffer;
2320 clenv->errors = 0;
2321 clenv->timeout = timeout;
2322 clenv->c_hostname = clientlnode->hostname;
2323 clenv->c_ipaddr = ipaddr;
2324 clenv->id = id;
2325 clenv->iface = iface;
2326 clenv->aclenv = aclenv;
2327
2328 reply(fdb, 220, FALSE, "%s (%s (%s)) Service ready",
2329 iface->hostname, DAEMON_NAME, DAEMON_VERSION);
2330 state = STATE_ALL;
2331 dstatus = MMS_NORMAL;
2332
e89b4e26
MM
2333 mmstat(&clenv->pstat, STAT_UPDATE, 1,
2334 "mmsmtpd|total|connections");
47071c2b
MM
2335
2336 mmstat_transact(&clenv->vstat, TRUE);
2337 mmstat(&clenv->vstat, STAT_UPDATE, 1,
8303aa4f
MM
2338 "mmsmtpd|current|connections");
2339 mmstat(&clenv->vstat, STAT_UPDATE, 1, "mmsmtpd|who|%s",
47071c2b
MM
2340 clenv->c_ipaddr);
2341 mmstat_transact(&clenv->vstat, FALSE);
2342
2343 /* Main state switcher loop */
917e9cbb 2344 for (;;) {
399db776
MM
2345 u_int32_t chash;
2346 register struct commandnode *nod;
47071c2b
MM
2347
2348 fdbflushw(fdb);
5f8db290 2349 if ((len = fdbgets(fdb, buffer, 1023, FALSE)) > -1) {
47071c2b
MM
2350
2351 /* If there were too many errors, exit accordingly */
2352 if (clenv->errors > CONF.MAX_ERRORS) {
2353 reply(fdb, 421, FALSE, "Too many errors");
2354 dstatus = MMS_MANY_ERRORS;
2355 break;
2356 }
2357 /* Verify if command matches an existing one */
399db776
MM
2358 nod = NULL;
2359 if ((chash = mm_strpack32(buffer, 4)) != 0)
2360 nod = (struct commandnode *)hashtable_lookup(
2361 &command_table, &chash, sizeof(u_int32_t));
2362 if (nod != NULL) {
47071c2b
MM
2363 register int (*func)(clientenv *);
2364
399db776 2365 mmsyslog(nod->command->loglevel, LOGLEVEL,
95c67efd 2366 "%08X < %s", id, buffer);
47071c2b 2367
5f8db290
MM
2368 if ((func = states[state].functions[nod->index])
2369 != NULL) {
47071c2b
MM
2370
2371 /* Valid command, process it in current state */
2372 nstate = func(clenv);
2373 if (nstate == STATE_END || nstate == STATE_ERROR)
2374 break;
2375 if (nstate != STATE_CURRENT)
2376 state = nstate;
2377
2378 } else {
2379 /* Unimplemented command for this state */
e334174e 2380 REGISTER_ERROR(clenv);
47071c2b
MM
2381 if (!reply(fdb, states[state].errcode, FALSE,
2382 states[state].errtext))
2383 break;
2384 }
2385
2386 } else {
95c67efd 2387 mmsyslog(3, LOGLEVEL, "%08X < %s", id, buffer);
47071c2b
MM
2388 reply(fdb, 500, FALSE,
2389 "Syntax error or unknown command, type HELP");
e334174e 2390 REGISTER_ERROR(clenv);
47071c2b
MM
2391 }
2392
2393 } else {
2394 /* Input error */
2395 if (len == FDB_TIMEOUT) {
2396 dstatus = MMS_INPUT_TIMEOUT;
2397 break;
2398 } else if (len == FDB_ERROR) {
2399 dstatus = MMS_INPUT_ERROR;
1a5bbe01
MM
2400 if (CONF.STATFAIL_EOF)
2401 mmstat(&clenv->pstat, STAT_UPDATE, 1,
8303aa4f 2402 "mmsmtpd|failed|eof|%s", clenv->c_ipaddr);
47071c2b
MM
2403 break;
2404 } else {
2405 dstatus = MMS_UNKNOWN;
2406 break;
2407 }
2408 }
2409
2410 }
2411
2412 messages_in = clenv->messages;
2413 rcpt_in = clenv->rcpts;
5eb34fba
MM
2414 data_in = FDBBYTESR(fdb);
2415 data_out = FDBBYTESW(fdb);
47071c2b
MM
2416
2417 mmstat_transact(&clenv->vstat, TRUE);
2418 mmstat(&clenv->vstat, STAT_UPDATE, -1,
8303aa4f 2419 "mmsmtpd|who|%s", clenv->c_ipaddr);
47071c2b 2420 mmstat(&clenv->vstat, STAT_UPDATE, -1,
8303aa4f 2421 "mmsmtpd|current|connections");
47071c2b
MM
2422 mmstat_transact(&clenv->vstat, FALSE);
2423
2424 mmstat_transact(&clenv->pstat, TRUE);
2425 mmstat(&clenv->pstat, STAT_UPDATE, messages_in,
8303aa4f 2426 "mmsmtpd|total|messages-in");
47071c2b 2427 mmstat(&clenv->pstat, STAT_UPDATE, data_in,
ddb6d6f7 2428 "mmsmtpd|total|bytes-in");
47071c2b 2429 mmstat(&clenv->pstat, STAT_UPDATE, data_out,
ddb6d6f7 2430 "mmsmtpd|total|bytes-out");
47071c2b
MM
2431 mmstat_transact(&clenv->pstat, FALSE);
2432
2433 /* Free our state-shared clenv */
2434 clenv = free_clientenv(clenv);
2435 } else
e89b4e26 2436 DEBUG_PRINTF("handleclient", "alloc_clientenv()");
47071c2b
MM
2437
2438 fdbclose(fdb);
2439 } else
e89b4e26 2440 DEBUG_PRINTF("handleclient", "fdbopen(%d)", fd);
47071c2b
MM
2441
2442 /* Log results */
2443 time_end = time(NULL);
2444 time_total = time_end - time_start;
2445 mmsyslog(1, LOGLEVEL,
e89b4e26
MM
2446 "%08X Closed: [%s] - (Seconds: %lu, Data in: %lld out: %lld, "
2447 "RCPTs: %lu, Messages in: %lu, Status: %s)",
2448 id, ipaddr, time_total, data_in, data_out, rcpt_in,
2449 messages_in, MMS_RSTRING(dstatus));
47071c2b
MM
2450
2451 return (0);
2452}
2453
2454
5eb34fba
MM
2455/* mmfd library thread support functions */
2456
2457
3dd832e3
MM
2458static pthread_mutexattr_t thread_ma;
2459
2460static void
2461thread_init(void)
2462{
2463 (void) pthread_mutexattr_init(&thread_ma);
2464 (void) pthread_mutexattr_settype(&thread_ma, PTHREAD_MUTEX_RECURSIVE);
2465}
2466
2467
5eb34fba 2468static void *
3dd832e3 2469thread_mutex_create(void)
5eb34fba
MM
2470{
2471 struct mutexnode *mnod;
2472
3dd832e3 2473 pthread_mutex_lock(&mutexes_lock);
399db776 2474 mnod = (struct mutexnode *)pool_alloc(&mutexes_pool, FALSE);
3dd832e3 2475 pthread_mutex_unlock(&mutexes_lock);
5eb34fba 2476
5f8db290 2477 if (mnod != NULL)
3dd832e3 2478 pthread_mutex_init(&mnod->mutex, &thread_ma);
5eb34fba
MM
2479
2480 return ((void *)mnod);
2481}
2482
2483
2484static void *
3dd832e3 2485thread_mutex_destroy(void *mtx)
5eb34fba 2486{
3dd832e3 2487 struct mutexnode *mnod = mtx;
5eb34fba 2488
3dd832e3
MM
2489 pthread_mutex_destroy(&mnod->mutex);
2490 pthread_mutex_lock(&mutexes_lock);
7a56f31f 2491 pool_free(mtx);
3dd832e3 2492 pthread_mutex_unlock(&mutexes_lock);
5eb34fba
MM
2493
2494 return (NULL);
2495}
2496
2497
2498static void
3dd832e3 2499thread_mutex_lock(void *mtx)
5eb34fba
MM
2500{
2501 struct mutexnode *mnod = mtx;
2502
3dd832e3 2503 pthread_mutex_lock(&mnod->mutex);
5eb34fba
MM
2504}
2505
2506
2507static void
3dd832e3 2508thread_mutex_unlock(void *mtx)
5eb34fba
MM
2509{
2510 struct mutexnode *mnod = mtx;
2511
3dd832e3 2512 pthread_mutex_unlock(&mnod->mutex);
6f933e0d
MM
2513}
2514
2515
e1089db9 2516static bool
3dd832e3 2517thread_eintr(void)
e1089db9
MM
2518{
2519 if (errno == EINTR)
2520 return TRUE;
2521
2522 return FALSE;
2523}
2524
2525
47071c2b
MM
2526/* Here are our real asynchroneous functions, called by the slave processes */
2527
2528
2529static void
2530async_resquery(struct async_msg *msg)
2531{
2532 struct async_resquery_msg *amsg = (void *)msg;
2533
2534 amsg->un.res.res = res_query(amsg->un.args.host, amsg->un.args.r_class,
2535 amsg->un.args.r_type, amsg->un.res.answer, 127);
2536}
2537
2538
2539/* And our wrapper functions calling the asynchroneous device */
2540
2541
2542static int
2543a_res_query(clientenv *clenv, const char *dname, int class, int type,
2544 u_char *answer, int anslen)
2545{
2546 struct async_resquery_msg *amsg = (void *)clenv->aclenv->msg;
2547 int res;
2548
2549 mm_strncpy(amsg->un.args.host, dname, 127);
2550 amsg->un.args.r_class = class;
2551 amsg->un.args.r_type = type;
2552 async_call(clenv->aclenv, ASYNC_RESQUERY);
2553 if ((res = amsg->un.res.res) != -1)
2554 mm_strncpy(answer, amsg->un.res.answer, anslen);
2555
2556 return (res);
2557}
904cd663
MM
2558
2559
399db776 2560/* Here consists of our hostnode expiration thread. It asynchroneously and
904cd663
MM
2561 * occasionally iterating through all the nodes to reset and/or expunge the
2562 * expired ones. Doing this here prevents interfering with the normally more
1c253c13
MM
2563 * frequent lookups which can be done with hashtable_lookup() in another
2564 * thread. We wouln't want those to need to iterate through all the nodes
978cad00
MM
2565 * everytime. We also call a function which ensures to delete any mailbox
2566 * files for which an entry exists in the boxdelete database table.
904cd663
MM
2567 */
2568/* ARGSUSED */
2569static void *
399db776 2570hosts_expire_thread(void *args)
904cd663 2571{
399db776 2572 struct hosts_expire_thread_iterator_udata data;
904cd663
MM
2573
2574 /* Set the initial timeout to the maximum allowed */
da634739 2575 data.soonest = CONF.FLOOD_EXPIRES * 60;
904cd663
MM
2576 for (;;) {
2577 /* Sleep until it is known that at least one node expired */
3dd832e3 2578 pthread_sleep(data.soonest);
904cd663
MM
2579 /* Tell our iterator function the current time and the maximum
2580 * allowed time to wait to
2581 */
2582 data.current = time(NULL);
da634739 2583 data.soonest = CONF.FLOOD_EXPIRES * 60;
399db776 2584 /* Lock hosts_table, expunge expired nodes and set data.soonest to the
904cd663
MM
2585 * time of the soonest next expireing node
2586 */
3dd832e3 2587 pthread_mutex_lock(&hosts_lock);
399db776
MM
2588 if (HASHTABLE_NODES(&hosts_table) > 0)
2589 hashtable_iterate(&hosts_table, hosts_expire_thread_iterator,
2590 &data);
3dd832e3 2591 pthread_mutex_unlock(&hosts_lock);
904cd663
MM
2592 }
2593
2594 /* NOTREACHED */
3dd832e3 2595 pthread_exit(NULL);
904cd663
MM
2596 return NULL;
2597}
2598
904cd663 2599static bool
399db776 2600hosts_expire_thread_iterator(hashnode_t *hnod, void *udata)
904cd663 2601{
399db776
MM
2602 hostnode *nod = (hostnode *)hnod;
2603 struct hosts_expire_thread_iterator_udata *data = udata;
da634739 2604 time_t rem;
904cd663 2605
da634739
MM
2606 /* If the node expired, free it. For nodes which do not, record the
2607 * soonest to expire node.
2608 */
2609 if ((rem = LR_REMAINS(&nod->lr, data->current)) == 0) {
2610 /* Entry expired, free it */
399db776
MM
2611 hashtable_unlink(&hosts_table, (hashnode_t *)nod);
2612 pool_free((pnode_t *)nod);
904cd663 2613 } else {
da634739
MM
2614 if (data->soonest > rem)
2615 data->soonest = rem;
904cd663 2616 }
da634739 2617
904cd663
MM
2618 return TRUE;
2619}
978cad00
MM
2620
2621
2622/*
2623 * This thread performs a verification for entries in the boxdelete table, and
2624 * deletes the dangling directories for boxes which have been deleted. This
2625 * way we do not need the frontend process to be able to delete arbitrary
2626 * files, while being able to provide an administration frontend to delete
2627 * mailboxes as needed. We also perform table optimization at regular
2628 * intervals.
2629 */
2630/* ARGSUSED */
2631static void *
2632db_gc_thread(void *args)
2633{
46173fd8 2634 PGconn *pgconn;
978cad00
MM
2635 int rounds;
2636
46173fd8
MM
2637 if ((pgconn = PQconnectdb(CONF.DB_INFO)) == NULL) {
2638 syslog(LOG_NOTICE, "do_gc_thread() - PQconnectdb()");
2639 exit(EXIT_FAILURE);
2640 }
2641
978cad00 2642 for (rounds = 1; ; rounds++) {
46173fd8
MM
2643 PGresult *pgres;
2644 bool ok;
978cad00 2645
3dd832e3 2646 (void) pthread_sleep(60);
978cad00
MM
2647
2648 /*
46173fd8
MM
2649 * Perform dangling mailbox directories cleanup every minute.
2650 * We do this in a transaction and automatically revert if we couldn't
2651 * delete an object for any reason. On success the sequence counter
2652 * is also reset. We use the id field for sorting.
978cad00 2653 */
46173fd8
MM
2654 if ((pgres = PQexec(pgconn, "BEGIN")) == NULL)
2655 continue;
2656 PQclear(pgres);
2657
2658 ok = TRUE;
2659 if ((pgres = PQexec(pgconn,
2660 "SELECT id,type,path FROM file_gc_queue ORDER BY id"))
2661 != NULL) {
2662 int i, t;
2663 PGresult *pgres2;
2664 unsigned long long id;
2665 char path[1024];
2666
2667 for (i = 0, t = PQntuples(pgres); i < t; i++) {
2668 id = strtoull(PQgetvalue(pgres, i, 0), NULL, 10);
2669 (void) snprintf(path, 1023, "%s/%s", CONF.MAIL_DIR,
2670 PQgetvalue(pgres, i, 2));
2671 if (*(PQgetvalue(pgres, i, 1)) == 'f') {
2672 if (unlink(path) != 0) {
2673 ok = FALSE;
2674 goto skip;
2675 }
2676 } else {
2677 if (rmdir(path) != 0) {
2678 ok = FALSE;
2679 goto skip;
978cad00 2680 }
46173fd8
MM
2681 }
2682skip:
2683 if (!ok) {
2684 mmsyslog(0, LOGLEVEL, "Couldn't delete '%s'", path);
2685 (void) snprintf(path, 1023,
2686 "DELETE FROM file_gc_queue WHERE id=%llu", id);
2687 if ((pgres2 = PQexec(pgconn, path)) != NULL)
2688 PQclear(pgres2);
2689 ok = TRUE;
2690 continue;
978cad00
MM
2691 }
2692 }
46173fd8
MM
2693 PQclear(pgres);
2694 }
2695 if (ok) {
2696 if ((pgres = PQexec(pgconn, "DELETE FROM file_gc_queue")) != NULL)
2697 PQclear(pgres);
2698 else
2699 ok = FALSE;
2700 }
2701 if (ok) {
2702 if ((pgres = PQexec(pgconn,
2703 "SELECT pg_catalog.setval(pg_catalog.pg_get_serial_sequence("
2704 "'file_gc_queue','id'),1,true)")) != NULL)
2705 PQclear(pgres);
2706 else
2707 ok = FALSE;
2708 }
2709 if (ok) {
2710 if ((pgres = PQexec(pgconn, "COMMIT")) != NULL)
2711 PQclear(pgres);
2712 } else {
2713 if ((pgres = PQexec(pgconn, "ROLLBACK")) != NULL)
2714 PQclear(pgres);
978cad00
MM
2715 }
2716
46173fd8
MM
2717 /*
2718 * Perform database optimizations every 24 hours.
2719 */
978cad00
MM
2720 if (rounds == 1440) {
2721 rounds = 0;
2722
10c624b0 2723#define OPTIMIZE(s) do { \
46173fd8
MM
2724 if ((pgres = PQexec(pgconn, "VACUUM ANALYZE " s)) != NULL) \
2725 PQclear(pgres); \
2726 else \
2727 mmsyslog(0, LOGLEVEL, "Couldn't optimize " s); \
10c624b0
MM
2728} while (/* CONSTCOND */0)
2729
2730 OPTIMIZE("alias");
2731 OPTIMIZE("box");
2732 OPTIMIZE("boxdelete");
46173fd8 2733 OPTIMIZE("file_gc_queue");
10c624b0
MM
2734 OPTIMIZE("filter");
2735 OPTIMIZE("mail");
2736 OPTIMIZE("nofrom");
2737 OPTIMIZE("relayfrom");
2738 OPTIMIZE("relaylocal");
2739 OPTIMIZE("relayqueue");
2740 OPTIMIZE("session");
2741 OPTIMIZE("user");
2742
2743#undef OPTIMIZE
978cad00
MM
2744 }
2745 }
2746
2747 /* NOTREACHED */
3dd832e3 2748 pthread_exit(NULL);
978cad00
MM
2749 return NULL;
2750}