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