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