*** empty log message ***
[mmondor.git] / mmsoftware / mmmail / src / mmsmtpd / mmsmtpd.c
CommitLineData
a4da92fc 1/* $Id: mmsmtpd.c,v 1.72 2005/03/26 11:45:48 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");
a4da92fc 85MMRCSID("$Id: mmsmtpd.c,v 1.72 2005/03/26 11:45:48 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
1757f1a7
MM
1297 if ((mysqlres = mmsql_query("SELECT nofrom_pattern FROM nofrom", 20))
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.
1686 * Basically, make sure that a header only comports alpha characters
1687 * and dashes in the name, and that the name follows by ': '.
de9989f3 1688 * Also allow continueing header lines which begin with spaces/tabs.
1bb24f38 1689 */
de9989f3
MM
1690 if (*line != '\t' && *line != ' ') {
1691 for (ptr = line; *ptr != '\0' && (isalpha(*ptr) || *ptr == '-');
1692 ptr++) ;
1693 if (*ptr != ':')
1694 goto endheader;
de9989f3 1695 }
1bb24f38
MM
1696
1697 /* Count number of Received: headers (SMTP hops) */
1698 if (mm_strncmp(line, "Received:", 9) == 0) {
47071c2b
MM
1699 ud->hops++;
1700 if (ud->hops > CONF.MAX_HOPS) {
1bb24f38 1701 /* Exceeded allowed number of hops, cancel reception */
47071c2b 1702 *res = CFDBRB_HOPS;
1bb24f38
MM
1703 return FDBRB_STOP;
1704 }
1705 return FDBRB_OK;
47071c2b 1706 }
47071c2b 1707
1bb24f38
MM
1708 /* Now verify for existance of headers we consider mandatory.
1709 * We'll create them if necessary.
1710 */
92bee4b2
MM
1711 /*
1712 * This one seems to not strictly follow RFC example as for case
1713 * sensitivity, I often see Message-ID: and Message-id:, which caused
1714 * mmmail to add an additional Message-Id: line if not doing this
1715 * check case insensitively.
1716 */
1717 if (mm_strncasecmp(line, "Message-Id:", 11) == 0)
1bb24f38
MM
1718 ud->msgid = TRUE;
1719 else if (mm_strncmp(line, "Date:", 5) == 0)
1720 ud->date = TRUE;
1721 else if (mm_strncmp(line, "From:", 5) == 0)
1722 ud->from = TRUE;
1723 else if (mm_strncmp(line, "To:", 3) == 0)
1724 ud->to = TRUE;
1725
1726 return FDBRB_OK;
1727
1728 } else {
1729
1730 /* Reading message body */
1731
856ca38a
MM
1732 /* ".." lines must be converted to "." ones */
1733 if (*len == 2 && line[0] == '.' && line[1] == '.') {
1734 line[1] = '\0';
47071c2b
MM
1735 (*len)--;
1736 }
1bb24f38
MM
1737
1738 return FDBRB_OK;
1739
47071c2b
MM
1740 }
1741
1bb24f38
MM
1742endheader:
1743
1744 /* We reached end of headers */
1745 ud->header = FALSE;
1746
1747 {
1748 char tline[1024], tdata[32];
1749
1750 /* Create the headers we consider mendatory if they were not supplied.
1751 * We append them after all headers that were supplied, this way the
1752 * Received: lines are guaranteed to be first. Note that this is only
1753 * safe if the total expansion we cause does not exceed 1024 bytes,
1754 * which buffer is guarenteed to have been reserved for a message line
1755 * by mmfd(3)'s fdbreadbuf(). Our additionnal expansion will never
1756 * exceed 320 bytes in this case.
1757 */
1758 *tline = '\0';
1759 if (!ud->msgid) {
1760 iso_time(tdata);
1761 (void) snprintf(tline, 1023, "Message-Id: <%s.%08lX-%lu@%s>\r\n",
1762 tdata, ud->clenv->id, ud->clenv->messages,
1763 ud->clenv->iface->hostname);
1764 }
1765 if (!ud->date) {
1766 rfc_time(tdata);
1767 (void) snprintf(tline, 1023, "%sDate: %s\r\n",
1768 tline, tdata);
1769 }
1770 if (!ud->from)
1771 (void) snprintf(tline, 1023, "%sFrom: %s\r\n",
1772 tline, ud->clenv->from);
1773 if (!ud->to)
1774 (void) snprintf(tline, 1023, "%sTo: undisclosed-recipients:;\r\n",
1775 tline);
1776
1777 if (*len == 0) {
1778 /* Valid end of header, an empty line. If no headers to add, all
1779 * is good. Otherwise, we must simply replace the current line by
1780 * the headers plus an empty line. Because the headers already
1781 * contain a newline, we just copy it over the current line,
1782 * fdbreadbuf() will append an additional one automatically.
1783 */
1784 if (*tline != '\0')
1785 *len = (mm_strcpy(line, tline) - line);
1786 } else {
1787 /* Invalid end of header, we must insert our headers, if any,
1788 * before the current line, along with an empty line.
1789 * Unfortunately, this could discard some bytes at the end of the
1790 * invalid last header line (the first body line) if it was too
1791 * long. However, this was a malformed message anyways and needed
1792 * major fixing. We could have errored instead if we were strict.
1793 */
1794 (void) mm_strncat(tline, "\r\n", 1023);
1795 (void) mm_strncat(tline, line, 1023);
1796 *len = (mm_strcpy(line, tline) - line);
1797 }
1798 }
1799
1800 return eres;
47071c2b
MM
1801}
1802
1803
1804/* This function is called by STATE_DATA and permits the client to send
1805 * the message data, respecting expected limits. Returns FALSE if the state
1bb24f38 1806 * should switch to STATE_ERROR, on fatal error (i.e. out of memory)
47071c2b
MM
1807 */
1808static bool
1809do_data(clientenv *clenv)
1810{
47071c2b
MM
1811 struct fdbrb_buffer *fdbrb;
1812 int res, err = DATA_INTERNAL;
1813 bool ok = FALSE;
47071c2b
MM
1814 struct validate_udata ud;
1815
1816 reply(clenv->fdb, data_msg[DATA_SUBMIT].code, FALSE,
1817 data_msg[DATA_SUBMIT].msg);
1818 fdbflushw(clenv->fdb);
1819
1820 /* Call our famous fdbreadbuf() which will read lines in a single buffer
1821 * and validate them via the validate_msg_line() function (above).
1822 * We restict the maximum length of a single line to 1024 characters
1823 * and are starting with an initial buffer of 32K, buffer which will
1824 * double in size whenever required. Of course don't read more than
1825 * CONF.MAX_DATA_SIZE bytes or CONF.MAX_DATA_LINES lines.
1826 * See mmfd(3) man page for details, and mmlib/mmfd.c
1827 */
1bb24f38
MM
1828 ud.hops = 0;
1829 ud.msgid = ud.date = ud.from = ud.to = FALSE;
1830 ud.header = TRUE;
1831 ud.clenv = clenv;
47071c2b
MM
1832 res = fdbreadbuf(&fdbrb, clenv->fdb, 32768, 1024, CONF.MAX_DATA_SIZE,
1833 CONF.MAX_DATA_LINES, validate_msg_line, &ud, FALSE);
1bb24f38 1834
47071c2b
MM
1835 /* Map results to DATA suitable ones */
1836 switch (res) {
1837 case FDBRB_MEM:
1838 mmsyslog(0, LOGLEVEL, "%08X * Out of memory", clenv->id);
1839 err = DATA_INTERNAL;
e334174e 1840 REGISTER_ERROR(clenv);
47071c2b
MM
1841 break;
1842 case FDBRB_OVERFLOW:
1843 mmsyslog(0, LOGLEVEL, "%08X * Message size too large", clenv->id);
1844 err = DATA_OVERFLOW;
e334174e 1845 REGISTER_ERROR(clenv);
47071c2b
MM
1846 break;
1847 case FDBRB_TIMEOUT:
1848 mmsyslog(0, LOGLEVEL, "%08X * Input timeout", clenv->id);
1849 if (CONF.STATFAIL_TIMEOUT)
8303aa4f 1850 mmstat(&clenv->pstat, STAT_UPDATE, 1, "mmsmtpd|failed|timeout|%s",
47071c2b
MM
1851 clenv->c_ipaddr);
1852 break;
1853 case FDBRB_EOF:
1854 mmsyslog(0, LOGLEVEL, "%08X * Unexpected EOF", clenv->id);
1855 break;
1856 case CFDBRB_HOPS:
1857 mmsyslog(0, LOGLEVEL, "%08X * Too many hops", clenv->id);
1858 err = DATA_HOPS;
e334174e 1859 REGISTER_ERROR(clenv);
47071c2b
MM
1860 break;
1861 case FDBRB_OK:
1862 ok = TRUE;
1863 break;
1864 }
1865
1866 if (ok) {
5f8db290
MM
1867
1868 /* XXX The following could easily simply be provided by separately
1869 * compiled mmsmtpd modules, designed to support multple storage
1870 * methods, as do_data_store() or such. But, we only support MySQL
1871 * and file storage for now... Which suffices for me.
47071c2b 1872 */
bac02e9e 1873
5f8db290
MM
1874#if defined(MMMAIL_MYSQL)
1875
1876 ok = do_data_mysql(clenv, fdbrb);
1877
1878#elif defined(MMMAIL_FILE)
1879
1880 ok = do_data_file(clenv, fdbrb);
1881
1882#else
1883#error "One of MMMAIL_MYSQL or MMMAIL_FILE must be #defined!"
1884#endif
47071c2b 1885
47071c2b
MM
1886 }
1887
5f8db290
MM
1888 fdbfreebuf(&fdbrb); /* Internally only frees if not already freed */
1889 if (ok)
1890 err = DATA_OK;
47071c2b
MM
1891 reply(clenv->fdb, data_msg[err].code, FALSE, data_msg[err].msg);
1892
1893 /* Reset mail state (and free RCPTs) */
1894 init_clientenv(clenv, FALSE);
1895
1896 return (ok);
1897}
1898
5f8db290 1899/* Create a Received: line, isolated to prevent code duplication among
193955a0 1900 * different storage methods. Returns length of received line in bytes.
5f8db290 1901 */
193955a0 1902inline static size_t
5f8db290
MM
1903do_data_received(char *line, size_t len, clientenv *clenv, rcptnode *rnode,
1904 const char *smtptime)
1905{
1bb24f38 1906 (void) snprintf(line, len - 1,
5f8db290
MM
1907 "Received: from %s ([%s] HELO=%s)\r\n\tby %s (%s) "
1908 "with SMTP\r\n\tid %08lX-%lu for <%s>;\r\n\t%s\r\n",
1bb24f38 1909 (clenv->c_hostname ? clenv->c_hostname : "(unresolved)"),
5f8db290 1910 clenv->c_ipaddr,
1bb24f38 1911 (clenv->helo ? clenv->helo : "(unidentified)"),
5f8db290
MM
1912 clenv->iface->hostname, DAEMON_VERSION, clenv->id,
1913 clenv->messages, rnode->foraddress, smtptime);
193955a0
MM
1914
1915 return mm_strlen(line);
5f8db290
MM
1916}
1917
1918/* Used to update mailbox quotas. Isolated to prevent code duplication among
1919 * different storage methods. Note that this must be done under a lock when
1920 * necessary for consistency with actual message storage data.
1921 */
1922inline static bool
1923do_data_update(rcptnode *rnode, size_t len)
1924{
1925 char line[1024];
1926 bool ok = TRUE;
1927
1928 snprintf(line, 1023,
1929 "UPDATE box SET box_size=box_size+%ld,"
1930 "box_msgs=box_msgs+1,box_in=NOW() WHERE "
1931 "box_address='%s'",
1932 (long)len, rnode->address);
1933 if (!mmsql_command(line, mm_strlen(line))) {
1934 DEBUG_PRINTF("do_data", "mmsql_command(%s)", line);
1935 ok = FALSE;
1936 }
47071c2b 1937
5f8db290
MM
1938 return ok;
1939}
1940
1941/* Record statistics using mmstat(3) facility, called by do_data to prevent
1942 * code duplication among different storage methods.
1943 */
1944static void
1945do_data_stats(clientenv *clenv, rcptnode *rnode, size_t len)
1946{
1947 char *domptr;
1948
1949 mmstat_transact(&clenv->pstat, TRUE);
1950
1951 /* Record per-box statistics. Note that when aliases are used, the actual
1952 * target mailbox is used.
1953 */
1954 mmstat(&clenv->pstat, STAT_UPDATE, 1, "mmmail|box|%s|messages-in",
1955 rnode->address);
1956 mmstat(&clenv->pstat, STAT_UPDATE, len, "mmmail|box|%s|bytes-in",
1957 rnode->address);
1958
1959 /* And per-domain ones. The address was previously validated successfully
1960 * and the '@' character is guarenteed to be present for mm_strchr().
1961 */
1962 domptr = mm_strchr(rnode->address, '@');
1963 domptr++;
1964 mmstat(&clenv->pstat, STAT_UPDATE, 1, "mmmail|domain|%s|messages-in",
1965 domptr);
1966 mmstat(&clenv->pstat, STAT_UPDATE, len, "mmmail|domain|%s|bytes-in",
1967 domptr);
1968
1969 mmstat_transact(&clenv->pstat, FALSE);
1970}
1971
1972
1973#if defined(MMMAIL_MYSQL)
1974
1975static bool
1976do_data_mysql(clientenv *clenv, struct fdbrb_buffer *fdbrb)
1977{
1978 char line[1024], line2[2048], smtptime[32], *tmp, *query;
1979 rcptnode *rnode;
1980 bool ok = TRUE;
1981
1982 /* Allocate query buffer for mysql_real_query(), should be large
1983 * enough to handle the worst of cases where each character would
1984 * be escaped to two chars, and must also hold the rest of the
1985 * query string. We first process the message data through
1986 * mysql_escape_string(), leaving enough room for the query and our
1987 * "Received:" line, which will be copied before the message buffer
1988 * for each RCPT. The message buffer will start at offset 2048
1989 * to make sure that there is enough room to insert the
1990 * RCPT-specific data (query+received).
1991 */
1992 if ((query = malloc((fdbrb->current * 2) + 2053)) != NULL) {
1993 size_t len, qlen, tlen, clen;
1994
1995 /* Prepare message buffer for mysql query */
1996 clen = fdbrb->current; /* Used after freeing buffer as well */
1997 tmp = &query[2048];
1998 tmp += mysql_escape_string(tmp, fdbrb->array, clen);
1999 *tmp++ = '\'';
2000 *tmp++ = ')';
2001 *tmp++ = '\0';
2002 qlen = tmp - &query[2048];
2003 rfc_time(smtptime);
2004
2005 /* For each RCPT, create query and execute it */
2006 DLIST_FOREACH(&clenv->rcpt, rnode) {
2007 /* Use the common message buffer, but append the query and
2008 * message line before it (in it's 2048 bytes free area)
2009 */
2010 do_data_received(line, 1024, clenv, rnode, smtptime);
2011 tlen = mm_strlen(line) + clen;
978cad00 2012 snprintf(line2, 255,
5f8db290
MM
2013 "INSERT INTO mail (mail_box,mail_created,mail_size,"
2014 "mail_data) VALUES('%s',NOW(),%ld,'",
2015 rnode->address, (long)tlen);
2016 tmp = line2 + mm_strlen(line2);
2017 tmp += mysql_escape_string(tmp, line, mm_strlen(line));
2018 len = tmp - line2;
2019 tmp = &query[2048 - len];
2020 mm_memcpy(tmp, line2, len);
2021
2022 /* Query buffer prepared, execute query. This glock is
2023 * required for safety between the two queries which have
2024 * to be performed within a single transaction. See
2025 * mmlib/mmsql.c for implementation details; Currently uses
2026 * MySQL GET_LOCK() and RELEASE_LOCK(), which contrary to
2027 * table locking will permit to only cause the current thread
2028 * to sleep rather than the whole process in this case.
2029 */
2030 mmsql_glock("mmmail_boxmail");
2031 if (!mmsql_command(tmp, qlen + len)) {
2032 mmsyslog(0, LOGLEVEL, "mmsql_command(%s)", tmp);
2033 ok = FALSE;
2034 break;
2035 } else {
2036 u_int64_t id;
2037
2038 /* Obtain auto-increment value usd in last command */
2039 id = mmsql_last_auto_id();
2040
2041 if (!(ok = do_data_update(rnode, tlen))) {
2042 /* Delete previous successful entry, since updating quota
2043 * information did not succeed, and it must always be
2044 * accurate according to actual mail data.
2045 */
2046 snprintf(line, 1023,
2047 "DELETE FROM mail WHERE mail_id=%llu", id);
2048 (void) mmsql_command(line, mm_strlen(line));
2049 }
2050 }
2051 mmsql_gunlock("mmmail_boxmail");
2052
2053 if (!ok)
2054 break;
2055
2056 /* Everything successful, record statistics */
2057 do_data_stats(clenv, rnode, tlen);
2058 }
2059
2060 free(query);
2061 } else {
2062 DEBUG_PRINTF("do_data",
2063 "malloc(%d)", (int)(fdbrb->current * 2) + 2053);
2064 REGISTER_ERROR(clenv);
2065 ok = FALSE;
2066 }
2067
2068 return ok;
2069}
2070
2071#elif defined(MMMAIL_FILE)
2072
193955a0
MM
2073/* Returns TRUE if the client address/hostname is allowed to relay messages
2074 * for non-local addresses, or FALSE otherwise, with reason set to either
2075 * RCPT_UNKNOWN (destination address on a locally handled domain but
2076 * unexisting) or RCPT_RELAY (relay denied for sender address/hostname).
2077 * If TRUE is returned, the post can be relayed, since it does not belong to
2078 * any local domains we are handling and that the client has relaying rights.
2079 */
2080bool
2081address_relay_allow(clientenv *clenv, int *reason, const char *addr)
5f8db290 2082{
193955a0
MM
2083 bool res = TRUE;
2084 char query[1024];
2085 const char *domain;
2086 MYSQL_RES *mysqlres;
5f8db290 2087
193955a0 2088 /* Is address to a local domain but unknown to us? */
5f8db290 2089
193955a0
MM
2090 /* We know that the supplied address is valid, it thus must have '@'.
2091 * Set domain pointer to start of domain name.
2092 */
2093 for (domain = addr; *domain != '@'; domain++) ;
2094 domain++;
2095 /* Query database entries and search for any matching pattern. */
2096 (void) snprintf(query, 1023, "SELECT relaylocal_pattern FROM relaylocal");
2097 if ((mysqlres = mmsql_query(query, mm_strlen(query))) != NULL) {
2098 if ((mysql_num_rows(mysqlres)) > 0) {
2099 char pat[64];
2100 MYSQL_ROW *row;
2101 unsigned long *lengths;
5f8db290 2102
193955a0
MM
2103 while ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres)) != NULL) {
2104 lengths = mysql_fetch_lengths(mysqlres);
2105 if (row[0] != NULL) {
2106 mm_memcpy(pat, row[0], lengths[0]);
2107 pat[lengths[0]] = '\0';
2108 if (best_match(domain, pat) != -1) {
2109 res = FALSE;
2110 *reason = RCPT_UNKNOWN;
2111 break;
2112 }
2113 }
2114 }
2115 } else
2116 DEBUG_PRINTF("address_relay_allow", "mysql_num_rows()");
2117 mysqlres = mmsql_free_result(mysqlres);
2118 } else
2119 DEBUG_PRINTF("address_relay_allow", "mmsql_query(%s)", query);
5f8db290 2120
193955a0
MM
2121 /* Return with error immediately if address is locally handled */
2122 if (!res)
2123 return res;
5f8db290 2124
193955a0
MM
2125 /* No, so it appears that it would need relaying. Is the client then
2126 * allowed to relay messages through us? Verify via the client's IP
2127 * address and/or hostname.
2128 */
5f8db290 2129
193955a0 2130 res = FALSE;
5f8db290 2131
193955a0
MM
2132 (void) snprintf(query, 1023, "SELECT relayfrom_pattern FROM relayfrom");
2133 if ((mysqlres = mmsql_query(query, mm_strlen(query))) != NULL) {
2134 if ((mysql_num_rows(mysqlres)) > 0) {
2135 char pat[64];
2136 MYSQL_ROW *row;
2137 unsigned long *lengths;
5f8db290 2138
193955a0
MM
2139 while ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres)) != NULL) {
2140 lengths = mysql_fetch_lengths(mysqlres);
2141 if (row[0] != NULL) {
2142 mm_memcpy(pat, row[0], lengths[0]);
2143 pat[lengths[0]] = '\0';
2144 if (clenv->c_ipaddr != NULL) {
2145 if (best_match(clenv->c_ipaddr, pat) != -1) {
2146 res = TRUE;
2147 break;
2148 }
2149 }
2150 if (clenv->c_hostname != NULL) {
2151 if (best_match(clenv->c_hostname, pat) != -1) {
2152 res = TRUE;
2153 break;
2154 }
2155 }
2156 }
5f8db290
MM
2157 }
2158 } else
193955a0
MM
2159 DEBUG_PRINTF("address_relay_allow", "mysql_num_rows()");
2160 mysqlres = mmsql_free_result(mysqlres);
2161 } else
2162 DEBUG_PRINTF("address_relay_allow", "mmsql_query(%s)", query);
5f8db290 2163
193955a0
MM
2164 if (!res)
2165 *reason = RCPT_RELAY;
5f8db290 2166
193955a0
MM
2167 return res;
2168}
5f8db290 2169
193955a0
MM
2170/* Produces time in the format yyyymmddhhmmss. Supplied string must at least
2171 * be 15 bytes (16 recommended).
2172 */
2173static void
2174iso_time(char *str)
2175{
2176 time_t secs;
2177 struct tm *gtim;
2178
2179 secs = time(NULL);
2180 gtim = gmtime(&secs);
2181
2182 (void) snprintf(str, 16, "%04d%02d%02d%02d%02d%02d",
2183 gtim->tm_year + 1900, gtim->tm_mon + 1, gtim->tm_mday,
2184 gtim->tm_hour, gtim->tm_min, gtim->tm_sec);
2185}
2186
2187/* Saves a message to disk, into the <box> directory, creating the directory
2188 * if needed. It ensures to create a unique filename in that directory. The
2189 * directory and the file will be located into MAIL_DIR. Locks should be held
2190 * as necessary before calling this function is atomic behavior is required
2191 * among other tasks. <recvline> consists of the "Received:" header which will
2192 * be written first, followed by the data held in the <fdbrb> buffer. The
2193 * fullpath to the created filename will be stored into supplied <path>, which
978cad00 2194 * must be at least 256 bytes.
193955a0
MM
2195 */
2196static bool
2197message_write(char *path, const char *recvline, size_t recvlen,
2198 struct fdbrb_buffer *fdbrb, const char *box)
2199{
2200 bool ok = FALSE;
2201 char filetime[16];
2202 int i, fd;
2203
2204 fd = -1;
2205
2206 /* Make sure that directory exists, performing an mkdir(2) which will
2207 * fail if it already does.
2208 */
978cad00 2209 (void) snprintf(path, 255, "%s/%s", CONF.MAIL_DIR, box);
193955a0
MM
2210 if (mkdir(path, 00750) == -1 && errno != EEXIST) {
2211 mmsyslog(0, LOGLEVEL, "mkdir(%s) == %s", path, strerror(errno));
2212 return FALSE;
2213 }
2214
2215 /* Generate unique filename to store the message within the mail
2216 * directory. We will make 64 retries maximum in an attempt to ensure
2217 * creation of a unique filename.
2218 */
2219 iso_time(filetime);
2220 for (i = 0; i < 64; i++) {
978cad00 2221 (void) snprintf(path, 255, "%s/%s/%s.%08X", CONF.MAIL_DIR,
193955a0
MM
2222 box, filetime, (u_int32_t)random());
2223 if ((fd = open(path, O_CREAT | O_EXCL | O_WRONLY, 00640)) != -1)
2224 break;
2225 }
2226
2227 /* Successfully created a new unique file, save message data.
2228 * We also verify the return value of close(2), but are not calling
2229 * fdatasync(2) or fsync(2) which would considerably impact
2230 * performance.
2231 */
2232 if (fd != -1) {
2233 if (write(fd, recvline, recvlen) == recvlen &&
2234 write(fd, fdbrb->array, fdbrb->current) == fdbrb->current
2235 && close(fd) == 0)
2236 ok = TRUE;
2237 else {
2238 mmsyslog(0, LOGLEVEL, "write()/close()|(%s) == %s",
2239 path, strerror(errno));
2240 (void) close(fd);
2241 (void) unlink(path);
5f8db290 2242 }
193955a0
MM
2243 } else
2244 mmsyslog(0, LOGLEVEL, "open(%s) == %s", path, strerror(errno));
5f8db290 2245
193955a0
MM
2246 return ok;
2247}
2248
2249/* For each RCPT, queue the message appropriately */
2250static bool
2251do_data_file(clientenv *clenv, struct fdbrb_buffer *fdbrb)
2252{
2253 char smtptime[32], recvline[1024];
2254 rcptnode *rnode;
2255 size_t recvlen;
2256 bool ok;
2257
2258 ok = TRUE;
2259 rfc_time(smtptime);
5f8db290 2260
193955a0
MM
2261 DLIST_FOREACH(&clenv->rcpt, rnode) {
2262 /* Create Received: line */
2263 recvlen = do_data_received(recvline, 1024, clenv, rnode, smtptime);
2264 /* Queue for relaying or into the mailbox if local */
2265 if (!rnode->relay)
2266 ok = do_data_queue_box(clenv, recvline, recvlen, fdbrb, rnode);
20e5cbf1
MM
2267 else {
2268 if (!CONF.RELAYING)
2269 ok = FALSE;
2270 else
2271 ok = do_data_queue_relay(clenv, recvline, recvlen, fdbrb,
2272 rnode);
2273 }
5f8db290
MM
2274 if (!ok)
2275 break;
193955a0
MM
2276 }
2277
2278 return ok;
2279}
2280
2281/* Queue a message to a local mailbox */
2282static bool
2283do_data_queue_box(clientenv *clenv, const char *recvline, size_t recvlen,
2284 struct fdbrb_buffer *fdbrb, rcptnode *rnode)
2285{
978cad00 2286 char line[1024], path[256];
193955a0
MM
2287 bool ok = TRUE;
2288
2289 /* Obtain global lock. This ensures that both file and database data
2290 * are in sync and between both mmsmtpd and mmpop3d. Moreover, it even
2291 * allows proper serialization of operations over NFS.
2292 */
2293 mmsql_glock("mmmail_boxmail");
5f8db290 2294
193955a0
MM
2295 if (message_write(path, recvline, recvlen, fdbrb, rnode->address)) {
2296 /* File written successfully, now write our corresponding MySQL
2297 * entries. Note that we store the absolute fullpath to the
2298 * message file into the database entry. Although this is not
2299 * necessary, it may prove useful later on.
2300 */
2301 (void) snprintf(line, 1023,
2302 "INSERT INTO mail (mail_box,mail_created,mail_size,"
2303 "mail_file) VALUES('%s',NOW(),%ld,'%s')",
2304 rnode->address, (long)fdbrb->current + recvlen, path);
2305 if (mmsql_command(line, mm_strlen(line))) {
2306 u_int64_t id;
2307
2308 /* Obtain auto-increment value used in last command */
2309 id = mmsql_last_auto_id();
2310
2311 if (!(ok = do_data_update(rnode, fdbrb->current + recvlen))) {
2312 /* Delete previous successful entry, since updating quota
2313 * information did not succeed, and it must always be
2314 * accurate according to actual mail data.
2315 */
2316 (void) snprintf(line, 1023,
2317 "DELETE FROM mail WHERE mail_id=%llu", id);
2318 (void) mmsql_command(line, mm_strlen(line));
2319 }
2320 } else {
2321 mmsyslog(0, LOGLEVEL, "mmsql_command(%s)", line);
2322 ok = FALSE;
2323 }
2324 /* If anything failed, delete stored message file. */
2325 if (!ok)
2326 (void) unlink(path);
2327 } else
2328 ok = FALSE;
2329
2330 /* We can finally safely release the global lock */
2331 mmsql_gunlock("mmmail_boxmail");
2332
2333 /* If everything successful, update mmstat statistics */
2334 if (ok)
5f8db290 2335 do_data_stats(clenv, rnode, fdbrb->current + recvlen);
193955a0
MM
2336
2337 return ok;
2338}
2339
2340/* Queue a message for relaying */
2341static bool
2342do_data_queue_relay(clientenv *clenv, const char *recvline, size_t recvlen,
2343 struct fdbrb_buffer *fdbrb, rcptnode *rnode)
2344{
978cad00 2345 char line[1024], path[256], *user, *domain, *restore;
193955a0
MM
2346 bool ok = TRUE;
2347
2348 /* This lock allows to maintain atomicity between the message file and
2349 * its corresponding database entry, between mmsmtpd(8) and mmrelayd(8).
2350 */
2351 mmsql_glock("mmmail_relayqueue");
2352
f2c550b1
MM
2353 /* We know that the address is valid in the rcpt node, separate it into
2354 * user and domain strings.
2355 */
2356 for (restore = rnode->address; *restore != '@'; restore++) ;
2357 *restore = '\0';
2358 user = rnode->address;
2359 domain = &restore[1];
2360
193955a0
MM
2361 if (message_write(path, recvline, recvlen, fdbrb, "relayqueue")) {
2362 /* Message file saved successfully, add corresponding DB entry */
2363 (void) snprintf(line, 1023,
2364 "INSERT INTO relayqueue (relayqueue_from,relayqueue_ipaddr,"
f2c550b1
MM
2365 "relayqueue_todomain,relayqueue_touser,relayqueue_size,"
2366 "relayqueue_file,relayqueue_queued) "
2367 "VALUES('%s','%s','%s','%s','%ld','%s',NOW())",
2368 clenv->from, clenv->c_ipaddr, domain, user,
193955a0
MM
2369 (long)fdbrb->current + recvlen, path);
2370 if (!mmsql_command(line, mm_strlen(line))) {
2371 /* SQL request failed, delete saved file */
2372 mmsyslog(0, LOGLEVEL, "mmsql_command(%s)", line);
2373 (void) unlink(path);
2374 ok = FALSE;
2375 }
2376 } else
2377 ok = FALSE;
2378
f2c550b1
MM
2379 /* Restore string to original value */
2380 *restore = '@';
2381
193955a0 2382 mmsql_gunlock("mmmail_relayqueue");
5f8db290 2383
12cbd451
MM
2384 /*
2385 * We now want to notify mmrelayd that it should verify for ready to
d6eecfe4
MM
2386 * relay mail as soon as possible instead of waiting until its next
2387 * scheduled round.
12cbd451 2388 */
20e5cbf1 2389 do_data_queue_notify(clenv);
12cbd451 2390
03d85e08 2391 return ok;
5f8db290
MM
2392}
2393
d6eecfe4
MM
2394/*
2395 * Attempt to notify mmrelayd(8) that at least one message is ready in the
2396 * queue to route.
20e5cbf1 2397 * XXX Broken! Do not use RELAYING = TRUE yet!
d6eecfe4
MM
2398 */
2399static void
20e5cbf1 2400do_data_queue_notify(clientenv *clenv)
d6eecfe4 2401{
20e5cbf1
MM
2402 bool ok = FALSE;
2403 /*
2404 notify_msg_t msg;
2405 */
2406
2407 /* XXX
2408 * We now actually require the lock, since we need to send exactly one
2409 * message per new queued mail. Fix accordingly. We however can fill in
2410 * the data for the message before sending it to hold the lock for as
2411 * short as possible. We also possibly only need it to verify if we're
2412 * connected, or to mark the socket disconnected... Since we only need
2413 * an atomic send per notification?
2414 */
d6eecfe4
MM
2415
2416 /*
2417 * If we cannot obtain lock, we know that it's already being notified, and
2418 * we don't need to do anything.
2419 */
2420 if (pth_mutex_acquire(&relayd_lock, TRUE, NULL) == FALSE)
2421 return;
2422
2423 /*
2424 * If socket wasn't open yet, attempt to open it. If we cannot, we have
2425 * nothing to notify, since the relay daemon most probably doesn't run.
2426 */
2427 if (relayd_sock == -1) {
2428 if ((relayd_sock = do_data_queue_notify_connect()) == -1)
2429 goto end;
2430 }
2431
2432 /*
2433 * Send a notification packet. If we cannot send it, attempt to reconnect
2434 * and send it again, but once only.
2435 */
2436 for (;;) {
2437 if (pth_write(relayd_sock, "N", 1) != 1) {
2438 (void) close(relayd_sock);
2439 if ((relayd_sock = do_data_queue_notify_connect()) != -1)
2440 continue;
2441 } else
2442 ok = TRUE;
2443 break;
2444 }
2445
2446end:
2447
2448 if (!ok)
2449 mmsyslog(0, LOGLEVEL,
2450 "mmrelayd(8) could not be notified (not running?)");
2451
2452 (void) pth_mutex_release(&relayd_lock);
2453}
2454
2455/*
2456 * Attempt to open the mmrelayd(8) notification socket, returning the
2457 * filedescriptor on success, or -1 on failure.
2458 */
2459static int
2460do_data_queue_notify_connect(void)
2461{
20e5cbf1 2462 int fd, opt;
d6eecfe4
MM
2463 struct sockaddr_un addr;
2464
2465 if ((fd = socket(AF_LOCAL, SOCK_DGRAM, 0)) != -1) {
20e5cbf1
MM
2466 opt = (int)BALIGN_CEIL(sizeof(notify_msg_t), 1024);
2467 if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &opt, sizeof(int)) == -1)
2468 mmsyslog(0, LOGLEVEL,
2469 "do_data_queue_notify_connect() - setsockopt() - (%s)",
2470 strerror(errno));
d6eecfe4 2471 mm_memclr(&addr, sizeof(struct sockaddr_un));
20e5cbf1 2472 (void) mm_strncpy(addr.sun_path, CONF.MMRELAYD_SOCKET_PATH, 100);
d6eecfe4
MM
2473 addr.sun_family = AF_UNIX;
2474 if ((pth_connect(fd, (struct sockaddr *)&addr,
2475 sizeof(struct sockaddr_un))) == -1) {
2476 (void) close(fd);
2477 fd = -1;
2478 } else
2479 mmsyslog(0, LOGLEVEL, "Cannot connect to mmrelayd(8) socket "
2480 "'%s' (%s)", addr.sun_path, strerror(errno));
2481 } else
2482 mmsyslog(0, LOGLEVEL, "Cannot create socket descriptor (%s)",
2483 strerror(errno));
2484
2485 return fd;
2486}
2487
5f8db290
MM
2488#else
2489#error "One of MMMAIL_MYSQL or MMMAIL_FILE must be #defined!"
2490#endif
47071c2b
MM
2491
2492
2493/* This is the main function that is called to serve a client.
2494 * It comports the main loop and state switcher.
2495 */
2496static int
2497handleclient(unsigned long id, int fd, clientlistnode *clientlnode,
2498 struct iface *iface, struct async_clenv *aclenv)
2499{
2500 char buffer[1024], ipaddr[20], *tmp;
2501 int len, state, nstate, timeout, dstatus;
2502 clientenv *clenv;
2503 struct sockaddr_in *sinaddr;
2504 fdbuf *fdb;
2505 int64_t data_in, data_out;
2506 unsigned long rcpt_in, messages_in, time_total;
2507 time_t time_start, time_end;
47071c2b
MM
2508
2509 data_in = data_out = rcpt_in = messages_in = 0;
2510 dstatus = MMS_RESOURCE_ERROR;
2511 timeout = clientlnode->timeout;
2512 clenv = NULL;
2513
2514 /* Obtain IP address of client */
2515 sinaddr = (struct sockaddr_in *)&clientlnode->client;
2516 if ((tmp = inet_ntoa(sinaddr->sin_addr))) mm_strncpy(ipaddr, tmp, 19);
2517 else mm_strncpy(ipaddr, "0.0.0.0", 8);
2518
5f8db290 2519 if (clientlnode->hostname != NULL)
47071c2b
MM
2520 /* Log user's address and hostname */
2521 mmsyslog(1, LOGLEVEL, "%08X Connect: [%s] - (%s)", id, ipaddr,
2522 clientlnode->hostname);
2523 else
2524 /* Log user's address only */
2525 mmsyslog(1, LOGLEVEL, "%08X Connect: [%s]", id, ipaddr);
2526
2527 time_start = time(NULL);
2528
5eb34fba 2529 if ((fdb = fdbopen(&gfdf, &fdbc, fd, 8192, 8192, CONF.BANDWIDTH_IN * 1024,
5f8db290
MM
2530 CONF.BANDWIDTH_OUT * 1024, timeout, timeout, FALSE))
2531 != NULL) {
47071c2b
MM
2532
2533 /* Allocate our clientenv to share with state functions */
5f8db290 2534 if ((clenv = alloc_clientenv()) != NULL) {
47071c2b
MM
2535
2536 /* Set some configuration options such as max_rcpts,
2537 * max_mesg_lines, max_mesg_size, hostname...
2538 */
2539 clenv->fdb = fdb;
2540 clenv->buffer = buffer;
2541 clenv->errors = 0;
2542 clenv->timeout = timeout;
2543 clenv->c_hostname = clientlnode->hostname;
2544 clenv->c_ipaddr = ipaddr;
2545 clenv->id = id;
2546 clenv->iface = iface;
2547 clenv->aclenv = aclenv;
2548
2549 reply(fdb, 220, FALSE, "%s (%s (%s)) Service ready",
2550 iface->hostname, DAEMON_NAME, DAEMON_VERSION);
2551 state = STATE_ALL;
2552 dstatus = MMS_NORMAL;
2553
e89b4e26
MM
2554 mmstat(&clenv->pstat, STAT_UPDATE, 1,
2555 "mmsmtpd|total|connections");
47071c2b
MM
2556
2557 mmstat_transact(&clenv->vstat, TRUE);
2558 mmstat(&clenv->vstat, STAT_UPDATE, 1,
8303aa4f
MM
2559 "mmsmtpd|current|connections");
2560 mmstat(&clenv->vstat, STAT_UPDATE, 1, "mmsmtpd|who|%s",
47071c2b
MM
2561 clenv->c_ipaddr);
2562 mmstat_transact(&clenv->vstat, FALSE);
2563
2564 /* Main state switcher loop */
917e9cbb 2565 for (;;) {
399db776
MM
2566 u_int32_t chash;
2567 register struct commandnode *nod;
47071c2b
MM
2568
2569 fdbflushw(fdb);
5f8db290 2570 if ((len = fdbgets(fdb, buffer, 1023, FALSE)) > -1) {
47071c2b
MM
2571
2572 /* If there were too many errors, exit accordingly */
2573 if (clenv->errors > CONF.MAX_ERRORS) {
2574 reply(fdb, 421, FALSE, "Too many errors");
2575 dstatus = MMS_MANY_ERRORS;
2576 break;
2577 }
2578 /* Verify if command matches an existing one */
399db776
MM
2579 nod = NULL;
2580 if ((chash = mm_strpack32(buffer, 4)) != 0)
2581 nod = (struct commandnode *)hashtable_lookup(
2582 &command_table, &chash, sizeof(u_int32_t));
2583 if (nod != NULL) {
47071c2b
MM
2584 register int (*func)(clientenv *);
2585
399db776 2586 mmsyslog(nod->command->loglevel, LOGLEVEL,
95c67efd 2587 "%08X < %s", id, buffer);
47071c2b 2588
5f8db290
MM
2589 if ((func = states[state].functions[nod->index])
2590 != NULL) {
47071c2b
MM
2591
2592 /* Valid command, process it in current state */
2593 nstate = func(clenv);
2594 if (nstate == STATE_END || nstate == STATE_ERROR)
2595 break;
2596 if (nstate != STATE_CURRENT)
2597 state = nstate;
2598
2599 } else {
2600 /* Unimplemented command for this state */
e334174e 2601 REGISTER_ERROR(clenv);
47071c2b
MM
2602 if (!reply(fdb, states[state].errcode, FALSE,
2603 states[state].errtext))
2604 break;
2605 }
2606
2607 } else {
95c67efd 2608 mmsyslog(3, LOGLEVEL, "%08X < %s", id, buffer);
47071c2b
MM
2609 reply(fdb, 500, FALSE,
2610 "Syntax error or unknown command, type HELP");
e334174e 2611 REGISTER_ERROR(clenv);
47071c2b
MM
2612 }
2613
2614 } else {
2615 /* Input error */
2616 if (len == FDB_TIMEOUT) {
2617 dstatus = MMS_INPUT_TIMEOUT;
2618 break;
2619 } else if (len == FDB_ERROR) {
2620 dstatus = MMS_INPUT_ERROR;
1a5bbe01
MM
2621 if (CONF.STATFAIL_EOF)
2622 mmstat(&clenv->pstat, STAT_UPDATE, 1,
8303aa4f 2623 "mmsmtpd|failed|eof|%s", clenv->c_ipaddr);
47071c2b
MM
2624 break;
2625 } else {
2626 dstatus = MMS_UNKNOWN;
2627 break;
2628 }
2629 }
2630
2631 }
2632
2633 messages_in = clenv->messages;
2634 rcpt_in = clenv->rcpts;
5eb34fba
MM
2635 data_in = FDBBYTESR(fdb);
2636 data_out = FDBBYTESW(fdb);
47071c2b
MM
2637
2638 mmstat_transact(&clenv->vstat, TRUE);
2639 mmstat(&clenv->vstat, STAT_UPDATE, -1,
8303aa4f 2640 "mmsmtpd|who|%s", clenv->c_ipaddr);
47071c2b 2641 mmstat(&clenv->vstat, STAT_UPDATE, -1,
8303aa4f 2642 "mmsmtpd|current|connections");
47071c2b
MM
2643 mmstat_transact(&clenv->vstat, FALSE);
2644
2645 mmstat_transact(&clenv->pstat, TRUE);
2646 mmstat(&clenv->pstat, STAT_UPDATE, messages_in,
8303aa4f 2647 "mmsmtpd|total|messages-in");
47071c2b 2648 mmstat(&clenv->pstat, STAT_UPDATE, data_in,
ddb6d6f7 2649 "mmsmtpd|total|bytes-in");
47071c2b 2650 mmstat(&clenv->pstat, STAT_UPDATE, data_out,
ddb6d6f7 2651 "mmsmtpd|total|bytes-out");
47071c2b
MM
2652 mmstat_transact(&clenv->pstat, FALSE);
2653
2654 /* Free our state-shared clenv */
2655 clenv = free_clientenv(clenv);
2656 } else
e89b4e26 2657 DEBUG_PRINTF("handleclient", "alloc_clientenv()");
47071c2b
MM
2658
2659 fdbclose(fdb);
2660 } else
e89b4e26 2661 DEBUG_PRINTF("handleclient", "fdbopen(%d)", fd);
47071c2b
MM
2662
2663 /* Log results */
2664 time_end = time(NULL);
2665 time_total = time_end - time_start;
2666 mmsyslog(1, LOGLEVEL,
e89b4e26
MM
2667 "%08X Closed: [%s] - (Seconds: %lu, Data in: %lld out: %lld, "
2668 "RCPTs: %lu, Messages in: %lu, Status: %s)",
2669 id, ipaddr, time_total, data_in, data_out, rcpt_in,
2670 messages_in, MMS_RSTRING(dstatus));
47071c2b
MM
2671
2672 return (0);
2673}
2674
2675
5eb34fba
MM
2676/* mmfd library thread support functions */
2677
2678
2679static void *
2680_pth_mutex_create(void)
2681{
2682 struct mutexnode *mnod;
2683
2684 pth_mutex_acquire(&mutexes_lock, FALSE, NULL);
399db776 2685 mnod = (struct mutexnode *)pool_alloc(&mutexes_pool, FALSE);
5eb34fba
MM
2686 pth_mutex_release(&mutexes_lock);
2687
5f8db290 2688 if (mnod != NULL)
5eb34fba
MM
2689 pth_mutex_init(&mnod->mutex);
2690
2691 return ((void *)mnod);
2692}
2693
2694
2695static void *
2696_pth_mutex_destroy(void *mtx)
2697{
2698 /* struct mutexnode *mnod = mtx; */
2699
2700 /* pth_mutex_destroy(&mnod->mutex); */
2701 pth_mutex_acquire(&mutexes_lock, FALSE, NULL);
7a56f31f 2702 pool_free(mtx);
5eb34fba
MM
2703 pth_mutex_release(&mutexes_lock);
2704
2705 return (NULL);
2706}
2707
2708
2709static void
2710_pth_mutex_lock(void *mtx)
2711{
2712 struct mutexnode *mnod = mtx;
2713
2714 pth_mutex_acquire(&mnod->mutex, FALSE, NULL);
2715}
2716
2717
2718static void
2719_pth_mutex_unlock(void *mtx)
2720{
2721 struct mutexnode *mnod = mtx;
2722
2723 pth_mutex_release(&mnod->mutex);
2724}
2725
2726
2727static void
2728_pth_thread_yield(void)
2729{
2730 pth_yield(NULL);
2731}
2732
2733
6f933e0d
MM
2734static void
2735_pth_thread_sleep(int secs)
2736{
2737 pth_sleep(secs);
2738}
2739
2740
e1089db9 2741static bool
e6f6121b 2742_pth_eintr(void)
e1089db9
MM
2743{
2744 if (errno == EINTR)
2745 return TRUE;
2746
2747 return FALSE;
2748}
2749
2750
47071c2b
MM
2751/* Here are our real asynchroneous functions, called by the slave processes */
2752
2753
2754static void
2755async_resquery(struct async_msg *msg)
2756{
2757 struct async_resquery_msg *amsg = (void *)msg;
2758
2759 amsg->un.res.res = res_query(amsg->un.args.host, amsg->un.args.r_class,
2760 amsg->un.args.r_type, amsg->un.res.answer, 127);
2761}
2762
2763
2764/* And our wrapper functions calling the asynchroneous device */
2765
2766
2767static int
2768a_res_query(clientenv *clenv, const char *dname, int class, int type,
2769 u_char *answer, int anslen)
2770{
2771 struct async_resquery_msg *amsg = (void *)clenv->aclenv->msg;
2772 int res;
2773
2774 mm_strncpy(amsg->un.args.host, dname, 127);
2775 amsg->un.args.r_class = class;
2776 amsg->un.args.r_type = type;
2777 async_call(clenv->aclenv, ASYNC_RESQUERY);
2778 if ((res = amsg->un.res.res) != -1)
2779 mm_strncpy(answer, amsg->un.res.answer, anslen);
2780
2781 return (res);
2782}
904cd663
MM
2783
2784
399db776 2785/* Here consists of our hostnode expiration thread. It asynchroneously and
904cd663
MM
2786 * occasionally iterating through all the nodes to reset and/or expunge the
2787 * expired ones. Doing this here prevents interfering with the normally more
1c253c13
MM
2788 * frequent lookups which can be done with hashtable_lookup() in another
2789 * thread. We wouln't want those to need to iterate through all the nodes
978cad00
MM
2790 * everytime. We also call a function which ensures to delete any mailbox
2791 * files for which an entry exists in the boxdelete database table.
904cd663
MM
2792 */
2793/* ARGSUSED */
2794static void *
399db776 2795hosts_expire_thread(void *args)
904cd663 2796{
399db776 2797 struct hosts_expire_thread_iterator_udata data;
904cd663
MM
2798
2799 /* Set the initial timeout to the maximum allowed */
da634739 2800 data.soonest = CONF.FLOOD_EXPIRES * 60;
904cd663
MM
2801 data.cnt = 0;
2802 for (;;) {
2803 /* Sleep until it is known that at least one node expired */
da634739 2804 pth_sleep((unsigned int)data.soonest);
904cd663
MM
2805 /* Tell our iterator function the current time and the maximum
2806 * allowed time to wait to
2807 */
2808 data.current = time(NULL);
da634739 2809 data.soonest = CONF.FLOOD_EXPIRES * 60;
399db776 2810 /* Lock hosts_table, expunge expired nodes and set data.soonest to the
904cd663
MM
2811 * time of the soonest next expireing node
2812 */
399db776
MM
2813 pth_mutex_acquire(&hosts_lock, FALSE, NULL);
2814 if (HASHTABLE_NODES(&hosts_table) > 0)
2815 hashtable_iterate(&hosts_table, hosts_expire_thread_iterator,
2816 &data);
2817 pth_mutex_release(&hosts_lock);
904cd663
MM
2818 }
2819
2820 /* NOTREACHED */
2821 pth_exit(NULL);
2822 return NULL;
2823}
2824
904cd663 2825static bool
399db776 2826hosts_expire_thread_iterator(hashnode_t *hnod, void *udata)
904cd663 2827{
399db776
MM
2828 hostnode *nod = (hostnode *)hnod;
2829 struct hosts_expire_thread_iterator_udata *data = udata;
da634739 2830 time_t rem;
904cd663 2831
da634739
MM
2832 /* If the node expired, free it. For nodes which do not, record the
2833 * soonest to expire node.
2834 */
2835 if ((rem = LR_REMAINS(&nod->lr, data->current)) == 0) {
2836 /* Entry expired, free it */
399db776
MM
2837 hashtable_unlink(&hosts_table, (hashnode_t *)nod);
2838 pool_free((pnode_t *)nod);
904cd663 2839 } else {
da634739
MM
2840 if (data->soonest > rem)
2841 data->soonest = rem;
904cd663 2842 }
da634739 2843
904cd663
MM
2844 /* If the cache is big, prevent from interfering with other threads */
2845 if ((data->cnt++) == 64) {
2846 data->cnt = 0;
2847 pth_yield(NULL);
2848 }
2849
2850 return TRUE;
2851}
978cad00
MM
2852
2853
2854/*
2855 * This thread performs a verification for entries in the boxdelete table, and
2856 * deletes the dangling directories for boxes which have been deleted. This
2857 * way we do not need the frontend process to be able to delete arbitrary
2858 * files, while being able to provide an administration frontend to delete
2859 * mailboxes as needed. We also perform table optimization at regular
2860 * intervals.
2861 */
2862/* ARGSUSED */
2863static void *
2864db_gc_thread(void *args)
2865{
2866 int rounds;
2867
2868 for (rounds = 1; ; rounds++) {
2869 MYSQL_RES *mysqlres;
2870
2871 (void) pth_sleep(60);
2872
2873 /*
2874 * Perform dangling mailbox directories cleanup
2875 */
2876 if ((mysqlres = mmsql_query("SELECT boxdelete_address FROM boxdelete",
2877 -1)) != NULL) {
2878 char addr[64];
2879 MYSQL_ROW *row;
2880 unsigned long *lengths;
2881
2882 while ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres)) != NULL) {
2883 lengths = mysql_fetch_lengths(mysqlres);
2884 if (row[0] != NULL) {
2885 char deladdr[64], query[1024];
2886 bool delete;
2887
2888 delete = FALSE;
2889 *deladdr = '\0';
2890 mm_memcpy(addr, row[0], lengths[0]);
2891 addr[lengths[0]] = '\0';
2892
2893 /*
2894 * Important security sanity checking: Make sure that
2895 * this actually consists of a valid address. We
2896 * wouldn't want to perform anything if arbitrary
2897 * entries were filled by a malicious user, bypassing
2898 * the HTTP frontend security somehow, or the MySQL
2899 * ones. We also don't need to do anything if we are not
2900 * using files for message storage.
2901 */
2902#if defined(MMMAIL_FILE)
a4da92fc 2903 if (valid_address(NULL, deladdr, 64, addr, HOST_NORES)) {
978cad00
MM
2904 MYSQL_RES *mysqlres2;
2905
2906 /*
2907 * And make sure that no box entry exists for it!
2908 */
2909 (void) snprintf(query, 1023,
2910 "SELECT box_address FROM box WHERE "
2911 "box_address='%s'", deladdr);
2912 if ((mysqlres2 = mmsql_query(query, mm_strlen(query)))
2913 != NULL) {
2914 if (mysql_num_rows(mysqlres2) == 0)
2915 delete = TRUE;
2916 (void) mmsql_free_result(mysqlres2);
2917 }
2918 }
2919#endif /* defined(MMMAIL_FILE) */
2920 /* Delete db entry unconditionally */
2921 (void) snprintf(query, 1023,
2922 "DELETE FROM boxdelete WHERE "
2923 "boxdelete_address='%s'", addr);
2924 (void) mmsql_command(query, mm_strlen(query));
2925 /* Perform actual deletion, only if safe to */
2926 if (delete)
2927 db_gc_thread_delete(deladdr);
2928 }
2929 }
2930 (void) mmsql_free_result(mysqlres);
2931 }
2932
2933 if (rounds == 1440) {
2934 rounds = 0;
2935
2936 /*
2937 * Perform database optimization every 24 hours
2938 */
10c624b0
MM
2939#define OPTIMIZE(s) do { \
2940 if ((mysqlres = mmsql_query("OPTIMIZE TABLE " s, -1)) != NULL) \
2941 (void) mmsql_free_result(mysqlres); \
2942} while (/* CONSTCOND */0)
2943
2944 OPTIMIZE("alias");
2945 OPTIMIZE("box");
2946 OPTIMIZE("boxdelete");
2947 OPTIMIZE("filter");
2948 OPTIMIZE("mail");
2949 OPTIMIZE("nofrom");
2950 OPTIMIZE("relayfrom");
2951 OPTIMIZE("relaylocal");
2952 OPTIMIZE("relayqueue");
2953 OPTIMIZE("session");
2954 OPTIMIZE("user");
2955
2956#undef OPTIMIZE
978cad00
MM
2957 }
2958 }
2959
2960 /* NOTREACHED */
2961 pth_exit(NULL);
2962 return NULL;
2963}
2964
2965static void
2966db_gc_thread_delete(const char *addr)
2967{
2968 char dirpath[256], filepath[256];
2969 DIR *dir;
2970 struct dirent ent, *res;
2971 int count;
2972
2973 /*
2974 * Now <path> holds the actual directory to delete mail from. We know
2975 * that there only exist first level files in it, and we must ensure to
2976 * delete all of them, as well as the actual mailbox directory afterwards.
2977 */
2978 if ((dir = opendir(dirpath)) == NULL) {
2979 syslog(LOG_NOTICE, "db_gc_thread_delete(%s) - opendir(%s) == %s",
2980 addr, dirpath, strerror(errno));
2981 return;
2982 }
2983
2984 count = 1;
2985 while (readdir_r(dir, &ent, &res) == 0 && res == &ent) {
2986 (void) snprintf(filepath, 255, "%s/%s", dirpath, ent.d_name);
2987 if (unlink(filepath) != 0)
2988 syslog(LOG_NOTICE, "db_gc_thread_delete(%s) - unlink(%s) == %s",
2989 addr, filepath, strerror(errno));
2990 if (++count == 64) {
2991 count = 0;
2992 (void) pth_yield(NULL);
2993 }
2994 }
2995 if (rmdir(dirpath) != 0)
2996 syslog(LOG_NOTICE, "db_gc_thread_delete(%s) - rmdir(%s) == %s",
2997 addr, dirpath, strerror(errno));
2998
2999 closedir(dir);
3000}