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