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