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