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