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