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