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