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