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