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