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