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