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