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