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