5e76f7979bc75d9ae68432a4e5a76b3ca5781b0e
[mmondor.git] / mmsoftware / mmmail / src / mmsmtpd / mmsmtpd.c
1 /* $Id: mmsmtpd.c,v 1.37 2003/10/11 11:20:23 mmondor Exp $ */
2
3 /*
4 * Copyright (C) 2001-2003, 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
46 #include <sys/socket.h>
47 #include <netinet/in.h>
48 #include <arpa/inet.h>
49 #include <arpa/nameser.h>
50 #include <resolv.h>
51
52 #include <syslog.h>
53
54 #include <pth.h>
55 #include <signal.h>
56 #include <time.h>
57
58 #include <ctype.h>
59
60 #include <mmtypes.h>
61 #include <mmreadcfg.h>
62 #include <mmfd.h>
63 #include <mmlist.h>
64 #include <mmpool.h>
65 #include <mmhash.h>
66 #include <mmserver.h>
67 #include <mmsql.h>
68 #include <mmlog.h>
69 #include <mmstr.h>
70 #include <mmstring.h>
71 #include <mmstat.h>
72
73 #include "mmsmtpd.h"
74
75
76
77
78 MMCOPYRIGHT("@(#) Copyright (c) 2002-2003\n\
79 \tMatthew Mondor. All rights reserved.\n");
80 MMRCSID("$Id: mmsmtpd.c,v 1.37 2003/10/11 11:20:23 mmondor Exp $");
81
82
83
84
85 /* GLOBAL VARIABLES */
86 /* This stores the global configuration options */
87 static CONFIG CONF;
88
89 /* Here consists of the commands we support */
90 static command commands[] = {
91 /* LogLevel, Cmd, Args, Description (NULL=unimplemented) */
92 {3, "NOOP", "NOOP", "Does nothing"},
93 {3, "RSET", "RSET", "Resets system to initial state"},
94 {3, "QUIT", "QUIT", "Disconnects, exits"},
95 {3, "HELP", "HELP [<topic>]", "Gives HELP information"},
96 {2, "HELO", "HELO <hostname>", "Permits to authenticate"},
97 {2, "MAIL", "MAIL FROM:<sender>", "Specifies sender of message"},
98 {2, "RCPT", "RCPT TO:<recipient>", "Specifies a recipient"},
99 {3, "DATA", "DATA", "Accepts the message ending with ."},
100 {4, "BEER", NULL, NULL},
101 {0, NULL, NULL, NULL}
102 };
103
104 /* The system is simple enough that only one state is required, each command
105 * function will perform it's own sanity checking to solidly simulate states.
106 */
107 static int (*state_all[])(clientenv *) = {
108 all_noop, /* NOOP */
109 all_rset, /* RSET */
110 all_quit, /* QUIT */
111 all_help, /* HELP */
112 all_helo, /* HELO */
113 all_mail, /* MAIL */
114 all_rcpt, /* RCPT */
115 all_data, /* DATA */
116 all_beer /* BEER */
117 };
118
119 /* The definitions of our many various states (-: */
120 static const struct state states[] = {
121 {state_all, 0, "Abnormal error"}
122 };
123
124 /* Used for mmsyslog() */
125 static int LOGLEVEL;
126
127 /* Used for clenv allocation buffering */
128 static pool_t clenv_pool;
129 static pth_mutex_t clenv_lock;
130
131 /* Used for the flood protection cache */
132 static pool_t hosts_pool;
133 static hashtable_t hosts_table;
134 static pth_mutex_t hosts_lock;
135
136 /* Used for rcpt allocation buffering */
137 static pool_t rcpt_pool;
138 static pth_mutex_t rcpt_lock;
139
140 /* Pool used to optimize creating/destroying mmfd mutexes */
141 static pth_mutex_t mutexes_lock;
142 static pool_t mutexes_pool;
143
144 /* For fast command lookup */
145 static pool_t command_pool;
146 static hashtable_t command_table;
147
148 /* Global bandwidth shaping fdb context */
149 static fdbcontext fdbc;
150
151 /* Quick index to RCPT command replies (see mmsmtpd.h for matching defines) */
152 static const struct reply_messages rcpt_msg[] = {
153 {250, "Recipient ok"},
154 {503, "Use MAIL first"},
155 {552, "Too many recipients"},
156 {501, "Invalid address"},
157 {250, "Recipient already added"},
158 {402, "Mailbox full, try again later"},
159 {402, "Rate exceeded, try again later"},
160 {452, "Internal error, contact administrator"}
161 };
162
163 /* Fast index to DATA replies (see headerfile for matching keywords) */
164 static const struct reply_messages data_msg[] = {
165 {354, "Submit message ending with a single ."},
166 {250, "Ok, mail delivered"},
167 {552, "Too much mail data"},
168 {552, "Too many hops"},
169 {452, "Internal error"}
170 };
171
172 /* Pth support for mmfd library (that library rocks my world :) */
173 static fdfuncs gfdf = {
174 malloc,
175 free,
176 pth_poll,
177 pth_read,
178 pth_write,
179 pth_sleep,
180 pth_usleep,
181 _pth_mutex_create,
182 _pth_mutex_destroy,
183 _pth_mutex_lock,
184 _pth_mutex_unlock,
185 _pth_thread_yield,
186 _pth_eintr
187 };
188
189
190
191
192 /* MAIN */
193
194 int
195 main(int argc, char **argv)
196 {
197 uid_t uid;
198 gid_t *gids;
199 char *conf_file = "/etc/mmsmtpd.conf";
200 int ngids, ret = -1;
201 long facility;
202 char *db_host;
203 bool strlist;
204 cres_t cres;
205 carg_t *cargp;
206 carg_t cargs[] = {
207 {CAT_STR, CAF_NONE, 1, 255, "CHROOT_DIR", CONF.CHROOT_DIR},
208 {CAT_STR, CAF_NONE, 1, 255, "PID_PATH", CONF.PID_PATH},
209 {CAT_STR, CAF_NONE, 1, 31, "USER", CONF.USER},
210 {CAT_STR, CAF_NONE, 1, 255, "GROUPS", CONF.GROUPS},
211 {CAT_STR, CAF_NONE, 1, 31, "LOG_FACILITY", CONF.LOG_FACILITY},
212 {CAT_STR, CAF_NONE, 1, 1023, "SERVER_NAMES", CONF.SERVER_NAMES},
213 {CAT_STR, CAF_NONE, 1, 1023, "LISTEN_IPS", CONF.LISTEN_IPS},
214 {CAT_STR, CAF_NONE, 1, 63, "DB_HOST", CONF.DB_HOST},
215 {CAT_STR, CAF_NONE, 1, 31, "DB_USER", CONF.DB_USER},
216 {CAT_STR, CAF_NONE, 1, 31, "DB_PASSWORD", CONF.DB_PASSWORD},
217 {CAT_STR, CAF_NONE, 1, 31, "DB_DATABASE", CONF.DB_DATABASE},
218 {CAT_VAL, CAF_NONE, 1, 32, "ASYNC_PROCESSES", &CONF.ASYNC_PROCESSES},
219 {CAT_VAL, CAF_NONE, 1, 9999, "ALLOC_BUFFERS", &CONF.ALLOC_BUFFERS},
220 {CAT_VAL, CAF_NONE, 0, 4, "LOG_LEVEL", &CONF.LOG_LEVEL},
221 {CAT_VAL, CAF_NONE, 1, 65535, "LISTEN_PORT", &CONF.LISTEN_PORT},
222 {CAT_VAL, CAF_NONE, 1, 1000, "MAX_ERRORS", &CONF.MAX_ERRORS},
223 {CAT_VAL, CAF_NONE, 1, 99999, "MAX_IPS", &CONF.MAX_IPS},
224 {CAT_VAL, CAF_NONE, 1, 99999, "MAX_PER_IP", &CONF.MAX_PER_IP},
225 {CAT_VAL, CAF_NONE, 0, 99999, "CONNECTION_RATE",
226 &CONF.CONNECTION_RATE},
227 {CAT_VAL, CAF_NONE, 0, 99999, "CONNECTION_PERIOD",
228 &CONF.CONNECTION_PERIOD},
229 {CAT_VAL, CAF_NONE, 1, 99999, "INPUT_TIMEOUT", &CONF.INPUT_TIMEOUT},
230 {CAT_VAL, CAF_NONE, 0, 99999, "BANDWIDTH_IN", &CONF.BANDWIDTH_IN},
231 {CAT_VAL, CAF_NONE, 0, 99999, "BANDWIDTH_OUT", &CONF.BANDWIDTH_OUT},
232 {CAT_VAL, CAF_NONE, 0, 99999, "GBANDWIDTH_IN", &CONF.GBANDWIDTH_IN},
233 {CAT_VAL, CAF_NONE, 0, 99999, "GBANDWIDTH_OUT", &CONF.GBANDWIDTH_OUT},
234 {CAT_VAL, CAF_NONE, 1, 99999, "MAX_RCPTS", &CONF.MAX_RCPTS},
235 {CAT_VAL, CAF_NONE, 1, 999999, "MAX_DATA_LINES",
236 &CONF.MAX_DATA_LINES},
237 {CAT_VAL, CAF_NONE, 1, 99999999, "MAX_DATA_SIZE",
238 &CONF.MAX_DATA_SIZE},
239 {CAT_VAL, CAF_NONE, 1, 999, "MAX_HOPS", &CONF.MAX_HOPS},
240 {CAT_VAL, CAF_NONE, 1, 999999, "FLOOD_MESSAGES",
241 &CONF.FLOOD_MESSAGES},
242 {CAT_VAL, CAF_NONE, 1, 120, "FLOOD_EXPIRES", &CONF.FLOOD_EXPIRES},
243 {CAT_VAL, CAF_NONE, 50, 999999, "FLOOD_CACHE", &CONF.FLOOD_CACHE},
244 {CAT_BOOL, CAF_NONE, 0, 0, "RESOLVE_HOSTS", &CONF.RESOLVE_HOSTS},
245 {CAT_BOOL, CAF_NONE, 0, 0, "RESOLVE_HELO", &CONF.RESOLVE_HELO},
246 {CAT_BOOL, CAF_NONE, 0, 0, "RESOLVE_MX_MAIL", &CONF.RESOLVE_MX_MAIL},
247 {CAT_BOOL, CAF_NONE, 0, 0, "REQUIRE_HELO", &CONF.REQUIRE_HELO},
248 {CAT_BOOL, CAF_NONE, 0, 0, "FLOOD_PROTECTION", &CONF.FLOOD_PROTECTION},
249 {CAT_BOOL, CAF_NONE, 0, 0, "STATFAIL_ADDRESS",
250 &CONF.STATFAIL_ADDRESS},
251 {CAT_BOOL, CAF_NONE, 0, 0, "STATFAIL_FLOOD", &CONF.STATFAIL_FLOOD},
252 {CAT_BOOL, CAF_NONE, 0, 0, "STATFAIL_FULL", &CONF.STATFAIL_FULL},
253 {CAT_BOOL, CAF_NONE, 0, 0, "STATFAIL_TIMEOUT",
254 &CONF.STATFAIL_TIMEOUT},
255 {CAT_BOOL, CAF_NONE, 0, 0, "STATFAIL_EOF", &CONF.STATFAIL_EOF},
256 {CAT_BOOL, CAF_NONE, 0, 0, "DELAY_ON_ERROR", &CONF.DELAY_ON_ERROR},
257 {CAT_END, CAF_NONE, 0, 0, NULL, NULL}
258 };
259 cmap_t cmap[] = {
260 {"LOG_AUTH", LOG_AUTH},
261 {"LOG_AUTHPRIV", LOG_AUTHPRIV},
262 {"LOG_CRON", LOG_CRON},
263 {"LOG_DAEMON", LOG_DAEMON},
264 {"LOG_FTP", LOG_FTP},
265 {"LOG_LPR", LOG_LPR},
266 {"LOG_MAIL", LOG_MAIL},
267 {"LOG_NEWS", LOG_NEWS},
268 {"LOG_SYSLOG", LOG_SYSLOG},
269 {"LOG_USER", LOG_USER},
270 {"LOG_UUCP", LOG_UUCP},
271 {NULL, 0}
272 };
273 struct async_func afuncs[] = {
274 {async_resquery, sizeof(struct async_resquery_msg)},
275 {NULL, 0}
276 };
277 struct mmsql_threadsupport mmsqlfuncs = {
278 _pth_mutex_create,
279 _pth_mutex_destroy,
280 _pth_mutex_lock,
281 _pth_mutex_unlock,
282 _pth_thread_yield
283 };
284 mmstat_t vstat;
285 pth_t hosts_table_thread = NULL;
286 pth_attr_t threadattr;
287
288 /* Set defaults */
289 *CONF.CHROOT_DIR = 0;
290 mm_strcpy(CONF.PID_PATH, "/var/run/mmsmtpd.pid");
291 mm_strcpy(CONF.USER, "mmmail");
292 mm_strcpy(CONF.GROUPS, "mmmail,mmstat");
293 mm_strcpy(CONF.LOG_FACILITY, "LOG_AUTHPRIV");
294 mm_strcpy(CONF.SERVER_NAMES, "smtp.localhost");
295 mm_strcpy(CONF.LISTEN_IPS, "127.0.0.1");
296 mm_strcpy(CONF.DB_HOST, "localhost");
297 mm_strcpy(CONF.DB_USER, "mmmail");
298 mm_strcpy(CONF.DB_PASSWORD, "mmmailpassword");
299 mm_strcpy(CONF.DB_DATABASE, "mmmail");
300 CONF.ASYNC_PROCESSES = 3;
301 CONF.ALLOC_BUFFERS = 1;
302 CONF.LOG_LEVEL = 3;
303 CONF.LISTEN_PORT = 25;
304 CONF.MAX_ERRORS = 16;
305 CONF.MAX_IPS = 64;
306 CONF.MAX_PER_IP = 1;
307 CONF.CONNECTION_RATE = 10;
308 CONF.CONNECTION_PERIOD = 30;
309 CONF.INPUT_TIMEOUT = 900;
310 CONF.BANDWIDTH_IN = 16;
311 CONF.BANDWIDTH_OUT = 4;
312 CONF.GBANDWIDTH_IN = 0;
313 CONF.GBANDWIDTH_OUT = 0;
314 CONF.MAX_RCPTS = 16;
315 CONF.MAX_DATA_LINES = 15000;
316 CONF.MAX_DATA_SIZE = 1048576;
317 CONF.MAX_HOPS = 30;
318 CONF.FLOOD_MESSAGES = 20;
319 CONF.FLOOD_EXPIRES = 30;
320 CONF.FLOOD_CACHE = 100;
321 CONF.RESOLVE_HOSTS = FALSE;
322 CONF.RESOLVE_HELO = FALSE;
323 CONF.RESOLVE_MX_MAIL = FALSE;
324 CONF.REQUIRE_HELO = FALSE;
325 CONF.FLOOD_PROTECTION = TRUE;
326 CONF.STATFAIL_ADDRESS = TRUE;
327 CONF.STATFAIL_FLOOD = TRUE;
328 CONF.STATFAIL_FULL = TRUE;
329 CONF.STATFAIL_TIMEOUT = TRUE;
330 CONF.STATFAIL_EOF = TRUE;
331 CONF.DELAY_ON_ERROR = FALSE;
332
333 /* Advertize */
334 printf("\r\n+++ %s (%s)\r\n\r\n", DAEMON_NAME, DAEMON_VERSION);
335
336 /* Read config file */
337 if (argc == 2)
338 conf_file = argv[1];
339 if (!mmreadcfg(&cres, cargs, conf_file)) {
340 /* Error parsing configuration file, report which */
341 printf("\nError parsing '%s'\n", conf_file);
342 printf("Error : %s\n", mmreadcfg_strerr(cres.CR_Err));
343 if (*(cres.CR_Data)) printf("Data : %s\n", cres.CR_Data);
344 if ((cargp = cres.CR_Keyword) != NULL) {
345 printf("Keyword: %s\n", cargp->CA_Keyword);
346 printf("Minimum: %ld\n", cargp->CA_Min);
347 printf("Maximum: %ld\n", cargp->CA_Max);
348 }
349 if (cres.CR_Line != -1)
350 printf("Line : %d\n", cres.CR_Line);
351 printf("\n");
352 exit(-1);
353 }
354
355 /* Post parsing */
356 if (!mmmapstring(cmap, CONF.LOG_FACILITY, &facility)) {
357 printf("\nUnknown syslog facility %s\n\n", CONF.LOG_FACILITY);
358 exit(-1);
359 }
360 LOGLEVEL = CONF.LOG_LEVEL;
361 /* Translate to numbers the user and group we were told to run as */
362 if ((uid = mmgetuid(CONF.USER)) == -1) {
363 printf("\nUnknown user '%s'\n\n", CONF.USER);
364 exit(-1);
365 }
366 if (!(gids = mmgetgidarray(&ngids, CONF.GROUPS)) == -1) {
367 printf("\nOne of following groups unknown: '%s'\n\n", CONF.GROUPS);
368 exit(-1);
369 }
370 if (!(mm_strcmp(CONF.DB_HOST, "localhost"))) db_host = NULL;
371 else db_host = CONF.DB_HOST;
372
373 /* Finally init everything */
374 openlog(DAEMON_NAME, LOG_PID | LOG_NDELAY, facility);
375
376 #ifndef NODROPPRIVS
377 if ((getuid())) {
378 printf("\nOnly the super user may start this daemon\n\n");
379 syslog(LOG_NOTICE, "* Only superuser can start me");
380 exit(-1);
381 }
382 #else /* NODROPPRIVS */
383 if ((getuid()) == 0) {
384 printf("\nCompiled with NODROPPRIVS, refusing to run as uid 0\n\n");
385 syslog(LOG_NOTICE, "* NODROPPRIVS, refusing to run as uid 0");
386 exit(-1);
387 }
388 #endif /* !NODROPPRIVS */
389
390 /* In case we chroot(2), the following is a good idea to execute first */
391 mmstat_initialize();
392 mmstat_init(&vstat, TRUE, TRUE);
393 mmstat_transact(&vstat, TRUE);
394 mmstat(&vstat, STAT_DELETE, 0, "mmsmtpd|current|connections");
395 mmstat(&vstat, STAT_DELETE, 0, "mmsmtpd|who|*");
396 mmstat_transact(&vstat, FALSE);
397 res_init();
398 if (!(mmsql_open(db_host, CONF.DB_USER, CONF.DB_PASSWORD,
399 CONF.DB_DATABASE))) {
400 printf("\nCould not connect to MySQLd\n\n");
401 syslog(LOG_NOTICE, "* Could not connect to MySQLd");
402 exit(-1);
403 }
404
405 make_daemon(CONF.PID_PATH, CONF.CHROOT_DIR);
406
407 /* After possible chroot(2) */
408 async_init(afuncs, CONF.ASYNC_PROCESSES, uid, gids, ngids);
409
410 /* Things which shouldn't be part of the async pool processes */
411 pth_init();
412 async_init_pth();
413 pth_mutex_init(&clenv_lock);
414 pth_mutex_init(&hosts_lock);
415 pth_mutex_init(&rcpt_lock);
416 pth_mutex_init(&mutexes_lock);
417
418 /* Allocate necessary pools */
419 /* Client nodes */
420 pool_init(&clenv_pool, malloc, free, sizeof(clientenv),
421 (65536 * CONF.ALLOC_BUFFERS) / sizeof(clientenv), 0, 0);
422 /* RCPT nodes */
423 pool_init(&rcpt_pool, malloc, free, sizeof(rcptnode),
424 (16384 * CONF.ALLOC_BUFFERS) / sizeof(rcptnode), 0, 0);
425 /* Mutexes pool for mmfd */
426 pool_init(&mutexes_pool, malloc, free, sizeof(struct mutexnode),
427 (16384 * CONF.ALLOC_BUFFERS) / sizeof(struct mutexnode), 0, 0);
428 /* Rate nodes */
429 if (CONF.FLOOD_PROTECTION) {
430 pool_init(&hosts_pool, malloc, free, sizeof(hostnode),
431 CONF.FLOOD_CACHE, 1, 1);
432 hashtable_init(&hosts_table, CONF.FLOOD_CACHE, 1, malloc, free,
433 mm_memcmp, hashtable_hash, FALSE);
434 threadattr = pth_attr_new();
435 pth_attr_set(threadattr, PTH_ATTR_JOINABLE, TRUE);
436 hosts_table_thread = pth_spawn(threadattr, hosts_expire_thread, NULL);
437 }
438 /* mmstr nodes */
439 strlist = mmstrinit(malloc, free, 65536 * CONF.ALLOC_BUFFERS);
440
441 if (hash_commands(commands, 4) && POOL_VALID(&clenv_pool) &&
442 POOL_VALID(&rcpt_pool) && POOL_VALID(&mutexes_pool) && strlist &&
443 (!CONF.FLOOD_PROTECTION || (POOL_VALID(&hosts_pool) &&
444 HASHTABLE_VALID(&hosts_table) &&
445 hosts_table_thread != NULL))) {
446 fdbcinit(&gfdf, &fdbc, CONF.GBANDWIDTH_IN * 1024,
447 CONF.GBANDWIDTH_OUT * 1024);
448 mmsql_init(&mmsqlfuncs);
449
450 tcp_server("402 Server too busy, try again\r\n",
451 CONF.SERVER_NAMES, CONF.LISTEN_IPS, uid, gids, ngids,
452 CONF.MAX_IPS, CONF.MAX_PER_IP, CONF.CONNECTION_RATE,
453 CONF.CONNECTION_PERIOD, CONF.INPUT_TIMEOUT,
454 CONF.LISTEN_PORT, CONF.RESOLVE_HOSTS, handleclient);
455
456 mmfreegidarray(gids);
457 ret = 0;
458 mmsql_close();
459 mmsql_exit();
460 } else {
461 printf("\nOut of memory\n\n");
462 syslog(LOG_NOTICE, "* Out of memory");
463 }
464
465 if (strlist) mmstrexit();
466 if (hosts_table_thread != NULL) {
467 pth_abort(hosts_table_thread);
468 pth_join(hosts_table_thread, NULL);
469 }
470 if (HASHTABLE_VALID(&command_table))
471 hashtable_destroy(&command_table, FALSE);
472 if (POOL_VALID(&command_pool))
473 pool_destroy(&command_pool);
474 if (HASHTABLE_VALID(&hosts_table))
475 hashtable_destroy(&hosts_table, FALSE);
476 if (POOL_VALID(&mutexes_pool))
477 pool_destroy(&mutexes_pool);
478 if (POOL_VALID(&hosts_pool))
479 pool_destroy(&hosts_pool);
480 if (POOL_VALID(&rcpt_pool))
481 pool_destroy(&rcpt_pool);
482 if (POOL_VALID(&clenv_pool))
483 pool_destroy(&clenv_pool);
484
485 fdbcdestroy(&fdbc);
486 kill(0, SIGTERM);
487 exit(ret);
488 }
489
490
491 /* Here consists of our command functions */
492
493
494 static int
495 all_noop(clientenv *clenv)
496 {
497 reply(clenv->fdb, 250, FALSE, "Ok, nothing performed");
498
499 return (STATE_CURRENT);
500 }
501
502
503 static int
504 all_rset(clientenv *clenv)
505 {
506 int nextstate = STATE_CURRENT;
507 fdbuf *fdb = clenv->fdb;
508
509 if (clenv->buffer[4] == 0) {
510 if (!init_clientenv(clenv, TRUE))
511 nextstate = STATE_ERROR;
512 else
513 reply(fdb, 250, FALSE, "Reset state");
514 } else {
515 reply(fdb, 550, FALSE, "Command syntax error");
516 REGISTER_ERROR(clenv);
517 }
518
519 return (nextstate);
520 }
521
522
523 static int
524 all_quit(clientenv *clenv)
525 {
526 reply(clenv->fdb, 221, FALSE, "%s closing connection",
527 clenv->iface->hostname);
528
529 return (STATE_END);
530 }
531
532
533 static int
534 all_help(clientenv *clenv)
535 {
536 int col;
537 fdbuf *fdb = clenv->fdb;
538 char *args[3], *cmdline = clenv->buffer, *tmp;
539
540 /* First check if a topic was specified */
541 if ((col = mm_straspl(args, cmdline, 2)) == 2) {
542 u_int32_t chash;
543 struct commandnode *nod;
544
545 /* Help requested on a topic */
546 nod = NULL;
547 if ((chash = mm_strpack32(args[1], 4)) != 0)
548 nod = (struct commandnode *)hashtable_lookup(&command_table,
549 &chash, sizeof(u_int32_t));
550 col = 0;
551 if (nod != NULL) {
552 reply(fdb, 214, TRUE, nod->command->args);
553 reply(fdb, 214, TRUE, " %s", nod->command->desc);
554 col = 2;
555 }
556
557 if (col)
558 reply(fdb, 214, FALSE, "End of HELP information");
559 else {
560 reply(fdb, 504, FALSE, "Unknown HELP topic");
561 REGISTER_ERROR(clenv);
562 }
563
564 } else {
565 register int i;
566
567 /* No, display the topics */
568 reply(fdb, 214, TRUE, "Available topics:");
569 fdbwrite(fdb, "214-", 4);
570 col = 1;
571 for (i = 0; (tmp = commands[i].name); i++) {
572 if (commands[i].desc) {
573 if (col == 0) fdbwrite(fdb, "\r\n214-", 6);
574 col++;
575 if (col > 4) col = 0;
576 fdbprintf(fdb, " %s", tmp);
577 }
578 }
579 fdbwrite(fdb, "\r\n", 2);
580
581 reply(fdb, 214, TRUE, "For more information, use HELP <topic>");
582 reply(fdb, 214, FALSE, "End of HELP information");
583 }
584
585 return (STATE_CURRENT);
586 }
587
588
589 static int
590 all_helo(clientenv *clenv)
591 {
592 fdbuf *fdb = clenv->fdb;
593 char *args[3], *cmdline = clenv->buffer;
594
595 if ((mm_straspl(args, cmdline, 2)) == 2) {
596 if (!clenv->helo) {
597 if (valid_host(clenv, args[1],
598 CONF.RESOLVE_HELO ? HOST_RES : HOST_NORES, TRUE)) {
599 if ((clenv->helo = mmstrdup(args[1])) == NULL)
600 DPRINTF("all_helo", "mmstrdup(%s)", args[1]);
601 reply(fdb, 250, FALSE, "%s ok", clenv->iface->hostname);
602 } else {
603 reply(fdb, 501, FALSE, "Invalid hostname");
604 REGISTER_ERROR(clenv);
605 }
606 } else {
607 reply(fdb, 503, FALSE, "Duplicate HELO, use RSET or proceed");
608 REGISTER_ERROR(clenv);
609 }
610 } else {
611 reply(fdb, 550, FALSE, "Command syntax error");
612 REGISTER_ERROR(clenv);
613 }
614
615 return (STATE_CURRENT);
616 }
617
618
619 static int
620 all_mail(clientenv *clenv)
621 {
622 int nextstate = STATE_CURRENT;
623 fdbuf *fdb = clenv->fdb;
624 char addr[64];
625 bool valid;
626
627 if (!CONF.REQUIRE_HELO || clenv->helo) {
628
629 if (!clenv->from) {
630
631 valid = FALSE;
632 if (!(mm_strncasecmp(" FROM:<>", &clenv->buffer[4], 8))) {
633 /* Some systems use empty MAIL FROM like this, make sure
634 * that IP address or hostname is allowed to do this.
635 */
636 valid = check_nofrom(clenv->c_ipaddr, clenv->c_hostname);
637 if (valid) *addr = 0;
638 } else
639 valid = valid_address(clenv, addr, clenv->buffer,
640 (CONF.RESOLVE_MX_MAIL) ? HOST_RES_MX : HOST_NORES);
641
642 if (valid) {
643 if ((clenv->from = (char *)mmstrdup(addr)))
644 reply(fdb, 250, FALSE, "Sender ok");
645 else
646 nextstate = STATE_ERROR;
647 } else {
648 reply(fdb, 501, FALSE, "Invalid address");
649 REGISTER_ERROR(clenv);
650 }
651
652 } else {
653 reply(fdb, 503, FALSE, "Sender already specified");
654 REGISTER_ERROR(clenv);
655 }
656
657 } else {
658 reply(fdb, 503, FALSE, "Use HELO first");
659 REGISTER_ERROR(clenv);
660 }
661
662 return (nextstate);
663 }
664
665
666 static int
667 all_rcpt(clientenv *clenv)
668 {
669 int nextstate = STATE_CURRENT;
670 fdbuf *fdb = clenv->fdb;
671 char addr[64], foraddr[64], *line = clenv->buffer;
672 int reason;
673 bool valid;
674 long max_size, size, max_msgs, msgs;
675 u_int64_t ahash;
676
677 /* I have opted for an elimination process here as there are many cases
678 * which can cause an RCPT to be refused, and alot of indenting was to
679 * be avoided for clarity. Functions could also be used but it has not
680 * been necessary this far, and we want the code performance to be optimal.
681 */
682 valid = TRUE;
683 reason = RCPT_OK;
684
685 if (!clenv->from) {
686 valid = FALSE;
687 reason = RCPT_NOFROM;
688 }
689
690 /* First make sure to not allow more RCPTs than CONF.MAX_RCPTS */
691 if (valid) {
692 if (!(DLIST_NODES(&clenv->rcpt) < CONF.MAX_RCPTS)) {
693 valid = FALSE;
694 reason = RCPT_MAX;
695 if (CONF.STATFAIL_FLOOD)
696 mmstat(&clenv->pstat, STAT_UPDATE, 1,
697 "mmsmtpd|failed|flood|%s", clenv->c_ipaddr);
698 }
699 }
700
701 /* Verify if existing address, if it isn't verify for any alias
702 * matching it and of course check for address validity again for
703 * safety. This way we make sure that an alias pattern does not over-
704 * ride an existing address, and that we only archive a message into
705 * an existing mailbox.
706 */
707 if (valid) {
708 valid = FALSE;
709 if (valid_address(clenv, addr, line, HOST_NORES)) {
710 mm_strlower(addr);
711 mm_strcpy(foraddr, addr);
712 valid = local_address(addr, &max_size, &size, &max_msgs, &msgs);
713 if (!valid) {
714 if (check_alias(addr))
715 if (!(valid = local_address(addr, &max_size, &size,
716 &max_msgs, &msgs)))
717 mmsyslog(0, LOGLEVEL, "Invalid alias address (%s)",
718 addr);
719 }
720 }
721 if (!valid) {
722 reason = RCPT_INVALID;
723 if (CONF.STATFAIL_ADDRESS)
724 mmstat(&clenv->pstat, STAT_UPDATE, 1,
725 "mmsmtpd|failed|address|%s", clenv->c_ipaddr);
726 }
727 }
728
729 /* Make sure mailbox quota limits are respected */
730 if (valid) {
731 if (!((size <= max_size) && (msgs <= max_msgs))) {
732 mmsyslog(0, LOGLEVEL, "%s mailbox full (%ld,%ld %ld,%ld)",
733 addr, max_size, size, max_msgs, msgs);
734 valid = FALSE;
735 reason = RCPT_FULL;
736 if (CONF.STATFAIL_FULL)
737 mmstat(&clenv->pstat, STAT_UPDATE, 1,
738 "mmsmtpd|failed|full|%s", addr);
739 }
740 }
741
742 /* Make sure that we only allow one RCPT per mailbox (alias already
743 * redirected to it)
744 */
745 if (valid) {
746 register rcptnode *rnode;
747 register int cnt;
748
749 ahash = mm_strhash64(addr);
750 cnt = 0;
751 DLIST_FOREACH(&clenv->rcpt, rnode) {
752 if (rnode->hash == ahash) {
753 valid = FALSE;
754 reason = RCPT_EXISTS;
755 break;
756 }
757 cnt++;
758 if (cnt > 64) {
759 cnt = 0;
760 pth_yield(NULL);
761 }
762 }
763 }
764
765 /* If CONF.FLOOD_PROTECTION is TRUE, make sure that we respect the rate
766 * of CONF.FLOOD_MESSAGES within CONF.FLOOD_EXPIRES for this client.
767 */
768 if (valid && CONF.FLOOD_PROTECTION) {
769 register hostnode *hnod;
770 register size_t len;
771 register char *entry;
772
773 if (clenv->c_hostname != NULL)
774 entry = clenv->c_hostname;
775 else
776 entry = clenv->c_ipaddr;
777 len = mm_strlen(entry);
778
779 pth_mutex_acquire(&hosts_lock, FALSE, NULL);
780 /* First acquire our hostnode, or create it if required */
781 if ((hnod = (hostnode *)hashtable_lookup(&hosts_table, entry, len + 1))
782 != NULL) {
783 /* Found, check and update limits */
784 hnod->posts++;
785 if (hnod->posts > CONF.FLOOD_MESSAGES) {
786 valid = FALSE;
787 reason = RCPT_FLOOD;
788 mmsyslog(0, LOGLEVEL,
789 "%08X Considered flood and rejected (%ld message(s) \
790 within last %ld minute(s))", clenv->id, hnod->posts,
791 CONF.FLOOD_EXPIRES);
792 }
793 } else {
794 /* Create a new entry */
795 if ((hnod = (hostnode *)pool_alloc(&hosts_pool, FALSE)) != NULL) {
796 mm_memcpy(hnod->host, entry, len + 1);
797 hnod->expires = time(NULL) + (CONF.FLOOD_EXPIRES * 60);
798 hnod->posts = 1;
799 hashtable_link(&hosts_table, (hashnode_t *)hnod, entry,
800 len + 1);
801 } else {
802 valid = FALSE;
803 reason = RCPT_FLOOD;
804 mmsyslog(0, LOGLEVEL, "FLOOD_CACHE not large enough");
805 }
806 }
807 pth_mutex_release(&hosts_lock);
808
809 if (!valid && CONF.STATFAIL_FLOOD)
810 mmstat(&clenv->pstat, STAT_UPDATE, 1,
811 "mmsmtpd|failed|flood|%s", clenv->c_ipaddr);
812 }
813
814 /* Finally append new RCPT to list */
815 if (valid) {
816 register rcptnode *rnode;
817
818 reason = RCPT_ERROR;
819 pth_mutex_acquire(&rcpt_lock, FALSE, NULL);
820 rnode = (rcptnode *)pool_alloc(&rcpt_pool, FALSE);
821 pth_mutex_release(&rcpt_lock);
822 if (rnode) {
823 mm_strcpy(rnode->address, addr);
824 mm_strcpy(rnode->foraddress, foraddr);
825 rnode->hash = ahash;
826 DLIST_APPEND(&clenv->rcpt, (node_t *)rnode);
827 reason = RCPT_OK;
828 clenv->rcpts++;
829 } else {
830 DPRINTF("all_rcpt", "pool_alloc(rcpt_pool)");
831 nextstate = STATE_ERROR;
832 }
833 }
834
835 /* Reply with appropriate message */
836 if (reason != RCPT_OK)
837 REGISTER_ERROR(clenv);
838 reply(fdb, rcpt_msg[reason].code, FALSE, rcpt_msg[reason].msg);
839
840 return (nextstate);
841 }
842
843
844 static int
845 all_data(clientenv *clenv)
846 {
847 int nextstate = STATE_CURRENT;
848 fdbuf *fdb = clenv->fdb;
849
850 if (clenv->buffer[4] == 0) {
851 if (clenv->from) {
852 if (DLIST_NODES(&clenv->rcpt) > 0) {
853 if (!do_data(clenv))
854 nextstate = STATE_ERROR;
855 else
856 clenv->messages++;
857 } else {
858 reply(fdb, 502, FALSE, "Use RCPT first");
859 REGISTER_ERROR(clenv);
860 }
861 } else {
862 reply(fdb, 503, FALSE, "Use MAIL and RCPT first");
863 REGISTER_ERROR(clenv);
864 }
865 } else {
866 reply(fdb, 550, FALSE, "Command syntax error");
867 REGISTER_ERROR(clenv);
868 }
869
870 return (nextstate);
871 }
872
873
874 static int
875 all_beer(clientenv *clenv)
876 {
877 reply(clenv->fdb, 420, FALSE, "Here, enjoy!");
878
879 return (STATE_CURRENT);
880 }
881
882
883
884
885 /* Used to initialize command hash table */
886 static bool
887 hash_commands(struct command *cmd, size_t min)
888 {
889 int i;
890
891 /* We do not care for any unfreed resources, the program will free them
892 * and exit if we return FALSE.
893 */
894 if (!pool_init(&command_pool, malloc, free, sizeof(struct commandnode),
895 64, 1, 0) || !hashtable_init(&command_table, 64, 1, malloc,
896 free, commandnode_keycmp, commandnode_keyhash, TRUE))
897 return FALSE;
898
899 for (i = 0; cmd->name != NULL; cmd++, i++) {
900 struct commandnode *nod;
901
902 if ((nod = (struct commandnode *)pool_alloc(&command_pool, FALSE))
903 == NULL)
904 return FALSE;
905 if ((nod->hash = mm_strpack32(cmd->name, min)) == 0)
906 return FALSE;
907 nod->command = cmd;
908 nod->index = i;
909 if (!hashtable_link(&command_table, (hashnode_t *)nod, &nod->hash,
910 sizeof(u_int32_t))) {
911 DPRINTF("hash_commands", "hashtable_link(%s)", cmd->name);
912 return FALSE;
913 }
914 }
915
916 return TRUE;
917 }
918
919
920 /* A quick hashtable_hash() replacement which already deals with unique
921 * 32-bit values
922 */
923 /* ARGSUSED */
924 static u_int32_t
925 commandnode_keyhash(const void *data, size_t len)
926 {
927 return *((u_int32_t *)data);
928 }
929
930
931 /* A quick memcmp() replacement which only needs to compare two 32-bit values
932 */
933 /* ARGSUSED */
934 static int
935 commandnode_keycmp(const void *src, const void *dst, size_t len)
936 {
937 return *((u_int32_t *)src) - *((u_int32_t *)dst);
938 }
939
940
941 /* Function used to return standard RFC result strings to the client,
942 * and returns FALSE on error.
943 * NOTE: As we are no longer calling write() directly, but fdbprintf()
944 * buffering function instead, it is no longer necessary to check the reply()
945 * return value each time it is called. We made sure to ignore SIGPIPE,
946 * and we let the main state switcher loop check connection state via
947 * fdbgets().
948 */
949 static bool
950 reply(fdbuf *fdb, int code, bool cont, const char *fmt, ...)
951 {
952 char buf[1024];
953 va_list arg_ptr;
954 bool err = TRUE;
955
956 *buf = 0;
957 va_start(arg_ptr, fmt);
958 vsnprintf(buf, 1023, fmt, arg_ptr);
959 va_end(arg_ptr);
960
961 if (cont) err = fdbprintf(fdb, "%d-%s\r\n", code, buf);
962 else err = fdbprintf(fdb, "%d %s\r\n", code, buf);
963
964 mmsyslog(3, LOGLEVEL, "> %d (%s)", code, buf);
965
966 return (err);
967 }
968
969
970 /* Allocate and prepare a clenv. Returns NULL on error */
971 static clientenv *
972 alloc_clientenv(void)
973 {
974 clientenv *clenv;
975
976 pth_mutex_acquire(&clenv_lock, FALSE, NULL);
977 clenv = (clientenv *)pool_alloc(&clenv_pool, TRUE);
978 pth_mutex_release(&clenv_lock);
979
980 if (clenv) {
981 mmstat_init(&clenv->vstat, TRUE, TRUE);
982 mmstat_init(&clenv->pstat, TRUE, FALSE);
983 }
984
985 return (clenv);
986 }
987
988
989 /* Useful on RSET to reset initial clenv state,
990 * returns TRUE on success or FALSE on error
991 */
992 static bool
993 init_clientenv(clientenv *clenv, bool helo)
994 {
995 if (helo && clenv->helo) clenv->helo = mmstrfree(clenv->helo);
996 if (clenv->from) clenv->from = mmstrfree(clenv->from);
997 empty_rcpts(&clenv->rcpt);
998
999 return (TRUE);
1000 }
1001
1002
1003 /* Frees all ressources allocated by a clenv */
1004 static clientenv *
1005 free_clientenv(clientenv *clenv)
1006 {
1007 if (clenv->helo) mmstrfree(clenv->helo);
1008 if (clenv->from) mmstrfree(clenv->from);
1009 empty_rcpts(&clenv->rcpt);
1010
1011 pth_mutex_acquire(&clenv_lock, FALSE, NULL);
1012 pool_free((pnode_t *)clenv);
1013 pth_mutex_release(&clenv_lock);
1014
1015 return (NULL);
1016 }
1017
1018
1019 /* Useful to free all rcpts for a clientenv.
1020 * XXX If we used a pool_t per clientenv for these we would simply destroy
1021 */
1022 static void
1023 empty_rcpts(list_t *lst)
1024 {
1025 node_t *nod;
1026
1027 pth_mutex_acquire(&rcpt_lock, FALSE, NULL);
1028 while ((nod = DLIST_TOP(lst)) != NULL) {
1029 DLIST_UNLINK(lst, nod);
1030 pool_free((pnode_t *)nod);
1031 }
1032 pth_mutex_release(&rcpt_lock);
1033 }
1034
1035
1036 /* Checks in the list of aliases for any pattern matching the address, and
1037 * map it to the real address to redirect to, replacing supplied address.
1038 * The addr char array must at least be 64 bytes. Returns FALSE if no alias
1039 * exist for the address, or TRUE on success.
1040 * XXX Could possibly use an async function, if we allow the async processes
1041 * to connect to MySQLd.
1042 */
1043 static bool
1044 check_alias(char *addr)
1045 {
1046 bool res = FALSE;
1047 char *args[3], oaddr[64], query[1024];
1048
1049 if ((mm_strspl(args, addr, 2, '@')) == 2) {
1050 MYSQL_RES *mysqlres;
1051
1052 mm_strncpy(oaddr, args[0], 63);
1053 snprintf(query, 1024, "SELECT alias_pattern,alias_box FROM alias \
1054 WHERE alias_domain='%s'", args[1]);
1055 if ((mysqlres = mmsql_query(query, mm_strlen(query))) != NULL) {
1056 if ((mysql_num_rows(mysqlres)) > 0) {
1057 char pat[64];
1058 int cur = 0, max = -1, cnt = 0;
1059 MYSQL_ROW *row;
1060 unsigned long *lengths;
1061
1062 /* Find best match */
1063 while ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres))
1064 != NULL) {
1065 lengths = mysql_fetch_lengths(mysqlres);
1066 if (row[0] && row[1]) {
1067 mm_memcpy(pat, row[0], lengths[0]);
1068 pat[lengths[0]] = 0;
1069 if ((cur = best_match(oaddr, pat)) != -1) {
1070 if (cur > max) {
1071 /* Matches better, remember this one */
1072 max = cur;
1073 mm_memcpy(addr, row[1], lengths[1]);
1074 addr[lengths[1]] = 0;
1075 }
1076 }
1077 }
1078 cnt++;
1079 if (cnt > 64) {
1080 cnt = 0;
1081 pth_yield(NULL);
1082 }
1083 }
1084 if (max > -1)
1085 res = TRUE;
1086 else
1087 /* Restore old address we have destroyed */
1088 args[1][-1] = '@';
1089 }
1090 mysqlres = mmsql_free_result(mysqlres);
1091 }
1092
1093 }
1094
1095 return (res);
1096 }
1097
1098
1099 /* Depending on which is set of <addr> and/or <host>, returns TRUE if any
1100 * of both matched an entry.
1101 */
1102 static bool
1103 check_nofrom(const char *addr, const char *host)
1104 {
1105 bool res = FALSE;
1106 MYSQL_RES *mysqlres;
1107
1108 if (addr == NULL && host == NULL) return (FALSE);
1109
1110 if ((mysqlres = mmsql_query("SELECT nofrom_pattern FROM nofrom", 20))
1111 != NULL) {
1112 if ((mysql_num_rows(mysqlres)) > 0) {
1113 int cnt = 0;
1114 MYSQL_ROW *row;
1115 unsigned long *lengths;
1116
1117 while ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres)) != NULL) {
1118 lengths = mysql_fetch_lengths(mysqlres);
1119 if (row[0]) {
1120 char pat[64];
1121
1122 mm_memcpy(pat, row[0], lengths[0]);
1123 pat[lengths[0]] = 0;
1124 if (addr) {
1125 if ((best_match(addr, pat)) != -1) {
1126 res = TRUE;
1127 break;
1128 }
1129 }
1130 if (host) {
1131 if ((best_match(host, pat)) != -1) {
1132 res = TRUE;
1133 break;
1134 }
1135 }
1136 }
1137 cnt++;
1138 if (cnt > 64) {
1139 cnt = 0;
1140 pth_yield(NULL);
1141 }
1142 }
1143 }
1144 mysqlres = mmsql_free_result(mysqlres);
1145 }
1146
1147 return (res);
1148 }
1149
1150
1151 /* Returns -1 if <str> does not match <pat> '*' and '?' wildcards pattern.
1152 * Otherwise returns > -1, a value representing the number of literal
1153 * characters in <pat> which exactly matched <str>. This us useful to evaluate
1154 * the best match among a list of patterns.
1155 */
1156 static int
1157 best_match(const char *str, const char *pat)
1158 {
1159 int lit = 0;
1160
1161 for (; *pat != '*'; pat++, str++) {
1162 if (*str == '\0') {
1163 if (*pat != '\0')
1164 return -1;
1165 else
1166 return lit;
1167 }
1168 if (*str == *pat)
1169 lit++;
1170 else
1171 if(*pat != '?')
1172 return -1;
1173 }
1174 while (pat[1] == '*')
1175 pat++;
1176 do {
1177 register int tmp;
1178
1179 if ((tmp = best_match(str, pat + 1)) != -1)
1180 return (lit + tmp);
1181 } while (*str++ != '\0');
1182
1183 return -1;
1184 }
1185
1186
1187 /* Returns FALSE if this address doesn't exist in our local mailboxes.
1188 * Otherwise it returns information about the mailbox via supplied pointers.
1189 */
1190 static bool
1191 local_address(const char *address, long *maxsize, long *size, long *maxmsgs,
1192 long *msgs)
1193 {
1194 bool res = FALSE;
1195 char line[1024];
1196 MYSQL_RES *mysqlres;
1197 MYSQL_ROW *row;
1198 unsigned int fields;
1199 unsigned long *lengths;
1200
1201 /* Query mysql to see if this address exists, and get limits/status */
1202 snprintf(line, 1000,
1203 "SELECT box_max_size,box_size,box_max_msgs,box_msgs FROM box\
1204 WHERE box_address='%s'", address);
1205
1206 if ((mysqlres = mmsql_query(line, mm_strlen(line))) != NULL) {
1207
1208 if ((mysql_num_rows(mysqlres)) == 1
1209 && (row = (MYSQL_ROW *)mysql_fetch_row(mysqlres)) != NULL) {
1210 if ((fields = mysql_num_fields(mysqlres)) == 4) {
1211 lengths = mysql_fetch_lengths(mysqlres);
1212 if (row[0] && row[1] && row[2] && row[3]) {
1213 mm_memcpy(line, row[0], lengths[0]);
1214 line[lengths[0]] = 0;
1215 *maxsize = atol(line);
1216 mm_memcpy(line, row[1], lengths[1]);
1217 line[lengths[1]] = 0;
1218 *size = atol(line);
1219 mm_memcpy(line, row[2], lengths[2]);
1220 line[lengths[2]] = 0;
1221 *maxmsgs = atol(line);
1222 mm_memcpy(line, row[3], lengths[3]);
1223 line[lengths[3]] = 0;
1224 *msgs = atol(line);
1225 res = TRUE;
1226 } else
1227 DPRINTF("local_address", "row[x] == NULL");
1228 } else
1229 DPRINTF("local_address", "mysql_num_fields()");
1230 }
1231
1232 mysqlres = mmsql_free_result(mysqlres);
1233 } else
1234 DPRINTF("local_address", "mmsql_query(%s)", line);
1235
1236 return (res);
1237 }
1238
1239
1240 /* Fills str which should be at least 32 bytes in length with current time */
1241 static void
1242 rfc_time(char *str)
1243 {
1244 /* Thu, 07 Dec 2000 07:36:15 -0000 */
1245 const static char *days[] = {
1246 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
1247 };
1248 const static char *months[] = {
1249 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1250 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1251 };
1252 time_t secs;
1253 struct tm *gtim;
1254
1255 /* Calculate expiration time of the cookie */
1256 secs = time(NULL);
1257 gtim = gmtime(&secs);
1258
1259 snprintf(str, 32, "%s, %02d %s %04d %02d:%02d:%02d -0000",
1260 days[gtim->tm_wday], gtim->tm_mday, months[gtim->tm_mon],
1261 gtim->tm_year + 1900, gtim->tm_hour, gtim->tm_min,
1262 gtim->tm_sec);
1263 }
1264
1265
1266 /* Returns whether or not supplied address is valid, and if it is return the
1267 * parsed address in the supplied string. String should be at least 64 bytes.
1268 */
1269 static bool
1270 valid_address(clientenv *clenv, char *to, char *addr, int res)
1271 {
1272 char *ptr, *a, *h;
1273
1274 mm_strlower(addr);
1275
1276 /* First locate required @ */
1277 for (ptr = addr; *ptr != '\0' && *ptr != '@'; ptr++) ;
1278 if (*ptr == '\0') return (FALSE);
1279 h = ptr + 1;
1280 /* Then scan to the left */
1281 for (ptr--; ptr >= addr && VALID_CHAR(*ptr); ptr--) ;
1282 if (h - ptr < 3 || ptr < addr) return (FALSE);
1283 a = ++ptr;
1284 /* Now to the right */
1285 for (ptr = h; *ptr != '\0' && VALID_CHAR(*ptr); ptr++) ;
1286 if (ptr - h < 2) return (FALSE);
1287 *ptr = '\0';
1288 /* Now validate hostname part */
1289 if (valid_host(clenv, h, res, FALSE)) {
1290 mm_strncpy(to, a, 63);
1291 return (TRUE);
1292 }
1293
1294 return (FALSE);
1295 }
1296
1297
1298 static bool
1299 valid_host(clientenv *clenv, char *host, int res, bool addr)
1300 {
1301 register char *ptr;
1302
1303 if (addr && res != HOST_RES_MX && valid_ipaddress(host)) return (TRUE);
1304
1305 mm_strlower(host);
1306 /* First make sure all characters are valid */
1307 for (ptr = host; *ptr != '\0'; ptr++)
1308 if (!VALID_CHAR(*ptr)) return (FALSE);
1309
1310 /* Now verify that all parts of the hostname are starting with
1311 * an alphanumeric char
1312 */
1313 ptr = host;
1314 while (*ptr != '\0') {
1315 if (!isalnum(*ptr)) return (FALSE);
1316 /* Find next host part */
1317 while (*ptr != '\0' && *ptr != '.') ptr++;
1318 if (*ptr == '.') {
1319 ptr++;
1320 continue;
1321 }
1322 if (*ptr == '\0') break;
1323 ptr++;
1324 }
1325
1326 /* Hostname seems valid, last sanity checking test consists of optional
1327 * resolving
1328 */
1329 if (res != HOST_NORES) {
1330 char answer[64];
1331
1332 if (res == HOST_RES_MX) {
1333 /* Check for an MX DNS IP address entry for it */
1334 if ((a_res_query(clenv, host, C_IN, T_MX, answer,
1335 sizeof(answer) - 1)) == -1)
1336 return (FALSE);
1337 } else if (res == HOST_RES) {
1338 /* Check if hostname resolves to normal A record */
1339 if ((a_res_query(clenv, host, C_IN, T_A, answer,
1340 sizeof(answer) - 1)) == -1)
1341 return (FALSE);
1342 }
1343 }
1344
1345 return (TRUE);
1346 }
1347
1348
1349 /* Some more parsing magic for IP address sanity checking */
1350 static bool
1351 valid_ipaddress(const char *addr)
1352 {
1353 char unit[5], *uptr, *utptr;
1354 int units;
1355
1356 for (units = 0, uptr = unit, utptr = unit + 4; uptr < utptr; addr++) {
1357 if (*addr == '\0' || *addr == '.') {
1358 if (uptr > unit && units < 4) {
1359 register int n;
1360
1361 *uptr = '\0';
1362 n = atoi(unit);
1363 if (n < 0 || n > 255) break;
1364 uptr = unit;
1365 units++;
1366 } else return (FALSE);
1367 if (*addr == '\0') break;
1368 } else if (isdigit(*addr)) *uptr++ = *addr;
1369 else return (FALSE);
1370 }
1371 if (!(units == 4 && *addr == '\0')) return (FALSE);
1372
1373 return (TRUE);
1374 }
1375
1376
1377 static int
1378 validate_msg_line(char *line, ssize_t *len, int *res, void *udata)
1379 {
1380 register struct validate_udata *ud = udata;
1381
1382 /* Count hops */
1383 if (ud->hops != -1) {
1384 if (!(mm_strncmp(line, "Received:", 9))) {
1385 ud->hops++;
1386 if (ud->hops > CONF.MAX_HOPS) {
1387 /* Exceeded maximum allowed number of "Received:" lines */
1388 *res = CFDBRB_HOPS;
1389 return (FDBRB_STOP);
1390 } else ud->nhops = 0;
1391 } else {
1392 ud->nhops++;
1393 if (ud->nhops > 5)
1394 ud->hops = -1;
1395 }
1396 }
1397
1398 /* Process .* lines */
1399 if (*len) {
1400 if (*line == '.') {
1401 /* Only '.' on line, stop reading */
1402 if (*len == 1) return (FDBRB_STOP);
1403 /* Strip starting . from line */
1404 mm_memmov(line, line + 1, *len);
1405 (*len)--;
1406 }
1407 }
1408
1409 return (FDBRB_OK);
1410 }
1411
1412
1413 /* This function is called by STATE_DATA and permits the client to send
1414 * the message data, respecting expected limits. Returns FALSE if the state
1415 * should switch to STATE_ERROR, on fatal error (eg: out of memory)
1416 */
1417 static bool
1418 do_data(clientenv *clenv)
1419 {
1420 char line[512], line2[2048], smtptime[32], *tmp, *query;
1421 struct fdbrb_buffer *fdbrb;
1422 int res, err = DATA_INTERNAL;
1423 bool ok = FALSE;
1424 rcptnode *rnode;
1425 struct validate_udata ud;
1426
1427 reply(clenv->fdb, data_msg[DATA_SUBMIT].code, FALSE,
1428 data_msg[DATA_SUBMIT].msg);
1429 fdbflushw(clenv->fdb);
1430
1431 /* Call our famous fdbreadbuf() which will read lines in a single buffer
1432 * and validate them via the validate_msg_line() function (above).
1433 * We restict the maximum length of a single line to 1024 characters
1434 * and are starting with an initial buffer of 32K, buffer which will
1435 * double in size whenever required. Of course don't read more than
1436 * CONF.MAX_DATA_SIZE bytes or CONF.MAX_DATA_LINES lines.
1437 * See mmfd(3) man page for details, and mmlib/mmfd.c
1438 */
1439 ud.hops = ud.nhops = 0;
1440 res = fdbreadbuf(&fdbrb, clenv->fdb, 32768, 1024, CONF.MAX_DATA_SIZE,
1441 CONF.MAX_DATA_LINES, validate_msg_line, &ud, FALSE);
1442 /* Map results to DATA suitable ones */
1443 switch (res) {
1444 case FDBRB_MEM:
1445 mmsyslog(0, LOGLEVEL, "%08X * Out of memory", clenv->id);
1446 err = DATA_INTERNAL;
1447 REGISTER_ERROR(clenv);
1448 break;
1449 case FDBRB_OVERFLOW:
1450 mmsyslog(0, LOGLEVEL, "%08X * Message size too large", clenv->id);
1451 err = DATA_OVERFLOW;
1452 REGISTER_ERROR(clenv);
1453 break;
1454 case FDBRB_TIMEOUT:
1455 mmsyslog(0, LOGLEVEL, "%08X * Input timeout", clenv->id);
1456 if (CONF.STATFAIL_TIMEOUT)
1457 mmstat(&clenv->pstat, STAT_UPDATE, 1, "mmsmtpd|failed|timeout|%s",
1458 clenv->c_ipaddr);
1459 break;
1460 case FDBRB_EOF:
1461 mmsyslog(0, LOGLEVEL, "%08X * Unexpected EOF", clenv->id);
1462 break;
1463 case CFDBRB_HOPS:
1464 mmsyslog(0, LOGLEVEL, "%08X * Too many hops", clenv->id);
1465 err = DATA_HOPS;
1466 REGISTER_ERROR(clenv);
1467 break;
1468 case FDBRB_OK:
1469 ok = TRUE;
1470 break;
1471 }
1472
1473 if (ok) {
1474 /* Allocate query buffer for mysql_real_query(), should be large
1475 * enough to handle the worst of cases where each character would
1476 * be escaped to two chars, and must also hold the rest of the
1477 * query string. We first process the message data through
1478 * mysql_escape_string(), leaving enough room for the query and our
1479 * "Received:" line, which will be copied before the message buffer
1480 * for each RCPT. The message buffer will start at offset 2048
1481 * to make sure that there is enough room to insert the
1482 * RCPT-specific data (query+received).
1483 */
1484 if ((query = malloc((fdbrb->current * 2) + 2053))) {
1485 size_t len, qlen, tlen, clen;
1486 char *domptr;
1487
1488 /* Prepare message buffer for mysql query */
1489 clen = fdbrb->current; /* Used after freeing buffer as well */
1490 tmp = &query[2048];
1491 tmp += mysql_escape_string(tmp, fdbrb->array, clen);
1492 *tmp++ = '\'';
1493 *tmp++ = ')';
1494 *tmp++ = '\0';
1495 qlen = tmp - &query[2048];
1496 rfc_time(smtptime);
1497 fdbfreebuf(&fdbrb); /* Free immediately */
1498
1499 /* For each RCPT, create query and execute it */
1500 DLIST_FOREACH(&clenv->rcpt, rnode) {
1501 /* Use the common message buffer, but append the query and
1502 * message line before it (in it's 2048 bytes free area)
1503 */
1504 snprintf(line, 511, "Received: from %s ([%s] HELO=%s)\r\n\t\
1505 by %s (%s) with SMTP\r\n\tid %08lX-%lu for <%s>;\r\n\t%s\r\n",
1506 clenv->c_hostname ? clenv->c_hostname : "(unresolved)",
1507 clenv->c_ipaddr,
1508 clenv->helo ? clenv->helo : "(unidentified)",
1509 clenv->iface->hostname, DAEMON_VERSION, clenv->id,
1510 clenv->messages, rnode->foraddress, smtptime);
1511 tlen = mm_strlen(line) + clen;
1512 snprintf(line2, 511, "INSERT INTO mail (mail_box,\
1513 mail_created,mail_size,mail_data) VALUES('%s',NOW(),%ld,'", rnode->address,
1514 (long)tlen);
1515 tmp = line2 + mm_strlen(line2);
1516 tmp += mysql_escape_string(tmp, line, mm_strlen(line));
1517 len = tmp - line2;
1518 tmp = &query[2048 - len];
1519 mm_memcpy(tmp, line2, len);
1520
1521 /* Query buffer prepared, execute query. This glock is
1522 * required for safety between the two queries which have
1523 * to be performed within a single transaction. See
1524 * mmlib/mmsql.c for implementation details; Currently uses
1525 * MySQL GET_LOCK() and RELEASE_LOCK(), which contrary to
1526 * table locking will permit to only cause the current thread
1527 * to sleep rather than the whole process in this case.
1528 */
1529 mmsql_glock("mmmail_boxmail");
1530 if (!mmsql_command(tmp, qlen + len)) {
1531 DPRINTF("do_data", "mmsql_command(%s)", tmp);
1532 ok = FALSE;
1533 break;
1534 } else {
1535 snprintf(line, 1000, "UPDATE box SET box_size=box_size+\
1536 %ld,box_msgs=box_msgs+1,box_in=NOW() WHERE box_address='%s'",
1537 (long)tlen, rnode->address);
1538 if (!mmsql_command(line, mm_strlen(line))) {
1539 DPRINTF("do_data", "mmsql_command(%s)", line);
1540 ok = FALSE;
1541 }
1542 }
1543 mmsql_gunlock("mmmail_boxmail");
1544 if (!ok)
1545 break;
1546
1547 mmstat_transact(&clenv->pstat, TRUE);
1548 /* Record per-box statistics. Note that when aliases are
1549 * used, the actual target mailbox is used.
1550 */
1551 mmstat(&clenv->pstat, STAT_UPDATE, 1,
1552 "mmmail|box|%s|messages-in", rnode->address);
1553 mmstat(&clenv->pstat, STAT_UPDATE, tlen,
1554 "mmmail|box|%s|bytes-in", rnode->address);
1555 /* And per-domain ones. The address was previously validated
1556 * successfully and the '@' character is guarenteed to be
1557 * present for mm_strchr().
1558 */
1559 domptr = mm_strchr(rnode->address, '@');
1560 domptr++;
1561 mmstat(&clenv->pstat, STAT_UPDATE, 1,
1562 "mmmail|domain|%s|messages-in", domptr);
1563 mmstat(&clenv->pstat, STAT_UPDATE, tlen,
1564 "mmmail|domain|%s|bytes-in", domptr);
1565 mmstat_transact(&clenv->pstat, FALSE);
1566 }
1567
1568 free(query);
1569 } else {
1570 DPRINTF("do_data", "malloc(%d)", (int)(fdbrb->current * 2) + 2053);
1571 REGISTER_ERROR(clenv);
1572 ok = FALSE;
1573 }
1574 }
1575
1576 fdbfreebuf(&fdbrb); /* Internally only frees if not already freed */
1577 if (ok) err = DATA_OK;
1578 reply(clenv->fdb, data_msg[err].code, FALSE, data_msg[err].msg);
1579
1580 /* Reset mail state (and free RCPTs) */
1581 init_clientenv(clenv, FALSE);
1582
1583 return (ok);
1584 }
1585
1586
1587
1588
1589 /* This is the main function that is called to serve a client.
1590 * It comports the main loop and state switcher.
1591 */
1592 static int
1593 handleclient(unsigned long id, int fd, clientlistnode *clientlnode,
1594 struct iface *iface, struct async_clenv *aclenv)
1595 {
1596 char buffer[1024], ipaddr[20], *tmp;
1597 int len, state, nstate, timeout, dstatus;
1598 clientenv *clenv;
1599 struct sockaddr_in *sinaddr;
1600 fdbuf *fdb;
1601 int64_t data_in, data_out;
1602 unsigned long rcpt_in, messages_in, time_total;
1603 time_t time_start, time_end;
1604
1605 data_in = data_out = rcpt_in = messages_in = 0;
1606 dstatus = MMS_RESOURCE_ERROR;
1607 timeout = clientlnode->timeout;
1608 clenv = NULL;
1609
1610 /* Obtain IP address of client */
1611 sinaddr = (struct sockaddr_in *)&clientlnode->client;
1612 if ((tmp = inet_ntoa(sinaddr->sin_addr))) mm_strncpy(ipaddr, tmp, 19);
1613 else mm_strncpy(ipaddr, "0.0.0.0", 8);
1614
1615 if (clientlnode->hostname)
1616 /* Log user's address and hostname */
1617 mmsyslog(1, LOGLEVEL, "%08X Connect: [%s] - (%s)", id, ipaddr,
1618 clientlnode->hostname);
1619 else
1620 /* Log user's address only */
1621 mmsyslog(1, LOGLEVEL, "%08X Connect: [%s]", id, ipaddr);
1622
1623 time_start = time(NULL);
1624
1625 if ((fdb = fdbopen(&gfdf, &fdbc, fd, 8192, 8192, CONF.BANDWIDTH_IN * 1024,
1626 CONF.BANDWIDTH_OUT * 1024, timeout, timeout, FALSE))) {
1627
1628 /* Allocate our clientenv to share with state functions */
1629 if ((clenv = alloc_clientenv())) {
1630
1631 /* Set some configuration options such as max_rcpts,
1632 * max_mesg_lines, max_mesg_size, hostname...
1633 */
1634 clenv->fdb = fdb;
1635 clenv->buffer = buffer;
1636 clenv->errors = 0;
1637 clenv->timeout = timeout;
1638 clenv->c_hostname = clientlnode->hostname;
1639 clenv->c_ipaddr = ipaddr;
1640 clenv->id = id;
1641 clenv->iface = iface;
1642 clenv->aclenv = aclenv;
1643
1644 reply(fdb, 220, FALSE, "%s (%s (%s)) Service ready",
1645 iface->hostname, DAEMON_NAME, DAEMON_VERSION);
1646 state = STATE_ALL;
1647 dstatus = MMS_NORMAL;
1648
1649 mmstat(&clenv->pstat, STAT_UPDATE, 1, "mmsmtpd|total|connections");
1650
1651 mmstat_transact(&clenv->vstat, TRUE);
1652 mmstat(&clenv->vstat, STAT_UPDATE, 1,
1653 "mmsmtpd|current|connections");
1654 mmstat(&clenv->vstat, STAT_UPDATE, 1, "mmsmtpd|who|%s",
1655 clenv->c_ipaddr);
1656 mmstat_transact(&clenv->vstat, FALSE);
1657
1658 /* Main state switcher loop */
1659 for (;;) {
1660 u_int32_t chash;
1661 register struct commandnode *nod;
1662
1663 fdbflushw(fdb);
1664 if ((len = fdbgets(fdb, buffer, 1000, FALSE)) > -1) {
1665
1666 /* If there were too many errors, exit accordingly */
1667 if (clenv->errors > CONF.MAX_ERRORS) {
1668 reply(fdb, 421, FALSE, "Too many errors");
1669 dstatus = MMS_MANY_ERRORS;
1670 break;
1671 }
1672 /* Verify if command matches an existing one */
1673 nod = NULL;
1674 if ((chash = mm_strpack32(buffer, 4)) != 0)
1675 nod = (struct commandnode *)hashtable_lookup(
1676 &command_table, &chash, sizeof(u_int32_t));
1677 if (nod != NULL) {
1678 register int (*func)(clientenv *);
1679
1680 mmsyslog(nod->command->loglevel, LOGLEVEL,
1681 "%08X < %s", id, buffer);
1682
1683 if ((func = states[state].functions[nod->index])) {
1684
1685 /* Valid command, process it in current state */
1686 nstate = func(clenv);
1687 if (nstate == STATE_END || nstate == STATE_ERROR)
1688 break;
1689 if (nstate != STATE_CURRENT)
1690 state = nstate;
1691
1692 } else {
1693 /* Unimplemented command for this state */
1694 REGISTER_ERROR(clenv);
1695 if (!reply(fdb, states[state].errcode, FALSE,
1696 states[state].errtext))
1697 break;
1698 }
1699
1700 } else {
1701 mmsyslog(3, LOGLEVEL, "%08X < %s", id, buffer);
1702 reply(fdb, 500, FALSE,
1703 "Syntax error or unknown command, type HELP");
1704 REGISTER_ERROR(clenv);
1705 }
1706
1707 } else {
1708 /* Input error */
1709 if (len == FDB_TIMEOUT) {
1710 dstatus = MMS_INPUT_TIMEOUT;
1711 break;
1712 } else if (len == FDB_ERROR) {
1713 dstatus = MMS_INPUT_ERROR;
1714 if (CONF.STATFAIL_EOF)
1715 mmstat(&clenv->pstat, STAT_UPDATE, 1,
1716 "mmsmtpd|failed|eof|%s", clenv->c_ipaddr);
1717 break;
1718 } else {
1719 dstatus = MMS_UNKNOWN;
1720 break;
1721 }
1722 }
1723
1724 }
1725
1726 messages_in = clenv->messages;
1727 rcpt_in = clenv->rcpts;
1728 data_in = FDBBYTESR(fdb);
1729 data_out = FDBBYTESW(fdb);
1730
1731 mmstat_transact(&clenv->vstat, TRUE);
1732 mmstat(&clenv->vstat, STAT_UPDATE, -1,
1733 "mmsmtpd|who|%s", clenv->c_ipaddr);
1734 mmstat(&clenv->vstat, STAT_UPDATE, -1,
1735 "mmsmtpd|current|connections");
1736 mmstat_transact(&clenv->vstat, FALSE);
1737
1738 mmstat_transact(&clenv->pstat, TRUE);
1739 mmstat(&clenv->pstat, STAT_UPDATE, messages_in,
1740 "mmsmtpd|total|messages-in");
1741 mmstat(&clenv->pstat, STAT_UPDATE, data_in,
1742 "mmsmtpd|total|bytes-in");
1743 mmstat(&clenv->pstat, STAT_UPDATE, data_out,
1744 "mmsmtpd|total|bytes-out");
1745 mmstat_transact(&clenv->pstat, FALSE);
1746
1747 /* Free our state-shared clenv */
1748 clenv = free_clientenv(clenv);
1749 } else
1750 DPRINTF("handleclient", "alloc_clientenv()");
1751
1752 fdbclose(fdb);
1753 } else
1754 DPRINTF("handleclient", "fdbopen(%d)", fd);
1755
1756 /* Log results */
1757 time_end = time(NULL);
1758 time_total = time_end - time_start;
1759 mmsyslog(1, LOGLEVEL,
1760 "%08X Closed: [%s] - (Seconds: %lu, Data in: %lld out: %lld, \
1761 RCPTs: %lu, Messages in: %lu, Status: %s)", id, ipaddr, time_total, data_in,
1762 data_out, rcpt_in, messages_in, MMS_RSTRING(dstatus));
1763
1764 return (0);
1765 }
1766
1767
1768 /* mmfd library thread support functions */
1769
1770
1771 static void *
1772 _pth_mutex_create(void)
1773 {
1774 struct mutexnode *mnod;
1775
1776 pth_mutex_acquire(&mutexes_lock, FALSE, NULL);
1777 mnod = (struct mutexnode *)pool_alloc(&mutexes_pool, FALSE);
1778 pth_mutex_release(&mutexes_lock);
1779
1780 if (mnod)
1781 pth_mutex_init(&mnod->mutex);
1782
1783 return ((void *)mnod);
1784 }
1785
1786
1787 static void *
1788 _pth_mutex_destroy(void *mtx)
1789 {
1790 /* struct mutexnode *mnod = mtx; */
1791
1792 /* pth_mutex_destroy(&mnod->mutex); */
1793 pth_mutex_acquire(&mutexes_lock, FALSE, NULL);
1794 pool_free(mtx);
1795 pth_mutex_release(&mutexes_lock);
1796
1797 return (NULL);
1798 }
1799
1800
1801 static void
1802 _pth_mutex_lock(void *mtx)
1803 {
1804 struct mutexnode *mnod = mtx;
1805
1806 pth_mutex_acquire(&mnod->mutex, FALSE, NULL);
1807 }
1808
1809
1810 static void
1811 _pth_mutex_unlock(void *mtx)
1812 {
1813 struct mutexnode *mnod = mtx;
1814
1815 pth_mutex_release(&mnod->mutex);
1816 }
1817
1818
1819 static void
1820 _pth_thread_yield(void)
1821 {
1822 pth_yield(NULL);
1823 }
1824
1825
1826 static bool
1827 _pth_eintr(void)
1828 {
1829 if (errno == EINTR)
1830 return TRUE;
1831
1832 return FALSE;
1833 }
1834
1835
1836 /* Here are our real asynchroneous functions, called by the slave processes */
1837
1838
1839 static void
1840 async_resquery(struct async_msg *msg)
1841 {
1842 struct async_resquery_msg *amsg = (void *)msg;
1843
1844 amsg->un.res.res = res_query(amsg->un.args.host, amsg->un.args.r_class,
1845 amsg->un.args.r_type, amsg->un.res.answer, 127);
1846 }
1847
1848
1849 /* And our wrapper functions calling the asynchroneous device */
1850
1851
1852 static int
1853 a_res_query(clientenv *clenv, const char *dname, int class, int type,
1854 u_char *answer, int anslen)
1855 {
1856 struct async_resquery_msg *amsg = (void *)clenv->aclenv->msg;
1857 int res;
1858
1859 mm_strncpy(amsg->un.args.host, dname, 127);
1860 amsg->un.args.r_class = class;
1861 amsg->un.args.r_type = type;
1862 async_call(clenv->aclenv, ASYNC_RESQUERY);
1863 if ((res = amsg->un.res.res) != -1)
1864 mm_strncpy(answer, amsg->un.res.answer, anslen);
1865
1866 return (res);
1867 }
1868
1869
1870 /* Here consists of our hostnode expiration thread. It asynchroneously and
1871 * occasionally iterating through all the nodes to reset and/or expunge the
1872 * expired ones. Doing this here prevents interfering with the normally more
1873 * frequent lookups which can be done with hashtable_lookup() in another
1874 * thread. We wouln't want those to need to iterate through all the nodes
1875 * everytime.
1876 */
1877 /* ARGSUSED */
1878 static void *
1879 hosts_expire_thread(void *args)
1880 {
1881 struct hosts_expire_thread_iterator_udata data;
1882
1883 /* Set the initial timeout to the maximum allowed */
1884 data.soonest = time(NULL) + CONF.FLOOD_EXPIRES;
1885 data.cnt = 0;
1886 for (;;) {
1887 /* Sleep until it is known that at least one node expired */
1888 pth_sleep(data.soonest - time(NULL));
1889 /* Tell our iterator function the current time and the maximum
1890 * allowed time to wait to
1891 */
1892 data.current = time(NULL);
1893 data.soonest = data.current + CONF.FLOOD_EXPIRES;
1894 /* Lock hosts_table, expunge expired nodes and set data.soonest to the
1895 * time of the soonest next expireing node
1896 */
1897 pth_mutex_acquire(&hosts_lock, FALSE, NULL);
1898 if (HASHTABLE_NODES(&hosts_table) > 0)
1899 hashtable_iterate(&hosts_table, hosts_expire_thread_iterator,
1900 &data);
1901 pth_mutex_release(&hosts_lock);
1902 }
1903
1904 /* NOTREACHED */
1905 pth_exit(NULL);
1906 return NULL;
1907 }
1908
1909
1910 static bool
1911 hosts_expire_thread_iterator(hashnode_t *hnod, void *udata)
1912 {
1913 hostnode *nod = (hostnode *)hnod;
1914 struct hosts_expire_thread_iterator_udata *data = udata;
1915
1916 if (nod->expires <= data->current) {
1917 /* This entry expired, expunge it */
1918 hashtable_unlink(&hosts_table, (hashnode_t *)nod);
1919 pool_free((pnode_t *)nod);
1920 } else {
1921 /* We must find and record the soonest expireing node */
1922 if (data->soonest > nod->expires)
1923 data->soonest = nod->expires;
1924 }
1925 /* If the cache is big, prevent from interfering with other threads */
1926 if ((data->cnt++) == 64) {
1927 data->cnt = 0;
1928 pth_yield(NULL);
1929 }
1930
1931 return TRUE;
1932 }