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