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