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