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