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