*** empty log message ***
[mmondor.git] / mmsoftware / mmmail / src / mmsmtpd / mmsmtpd.c
CommitLineData
46cf2cd5 1/* $Id: mmsmtpd.c,v 1.6 2003/01/04 09:31:54 mmondor Exp $ */
47071c2b
MM
2
3/*
5eb34fba 4 * Copyright (C) 2001-2003, Matthew Mondor
47071c2b
MM
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. All advertising materials mentioning features or use of this software
16 * must display the following acknowledgement:
17 * This product includes software written by Matthew Mondor.
18 * 4. The name of Matthew Mondor may not be used to endorse or promote
19 * products derived from this software without specific prior written
20 * permission.
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
45#include <sys/socket.h>
46#include <netinet/in.h>
47#include <arpa/inet.h>
48#include <arpa/nameser.h>
49#include <resolv.h>
50
51#include <syslog.h>
52
53#include <pth.h>
54#include <signal.h>
55#include <time.h>
56
57#include <ctype.h>
58
59#include <mmtypes.h>
60#include <mmreadcfg.h>
61#include <mmfd.h>
62#include <mmlist.h>
63#include <mmserver.h>
64#include <mmsql.h>
65#include <mmlog.h>
66#include <mmstr.h>
67#include <mmstring.h>
68#include <mmstat.h>
69
70#include "mmsmtpd.h"
71
72
73
74
5eb34fba 75MMCOPYRIGHT("@(#) Copyright (c) 2002-2003\n\
47071c2b 76\tMatthew Mondor. All rights reserved.\n");
46cf2cd5 77MMRCSID("$Id: mmsmtpd.c,v 1.6 2003/01/04 09:31:54 mmondor Exp $");
47071c2b
MM
78
79
80
81
82/* GLOBAL VARIABLES */
83/* This stores the global configuration options */
84static CONFIG CONF;
85
86/* Here consists of the commands we support */
87static command commands[] = {
88 /* Hash, LogLevel, Cmd, Args, Description (NULL=unimplemented) */
89 {0, 3, "NOOP", "NOOP", "Does nothing"},
90 {0, 3, "RSET", "RSET", "Resets system to initial state"},
91 {0, 3, "QUIT", "QUIT", "Disconnects, exits"},
92 {0, 3, "HELP", "HELP [<topic>]", "Gives HELP information"},
93 {0, 2, "HELO", "HELO <hostname>", "Permits to authenticate"},
94 {0, 2, "MAIL", "MAIL FROM:<sender>", "Specifies sender of message"},
95 {0, 2, "RCPT", "RCPT TO:<recipient>", "Specifies a recipient"},
96 {0, 3, "DATA", "DATA", "Accepts the message ending with ."},
97 {0, 4, "BEER", NULL, NULL},
98 {0, 0, NULL, NULL, NULL}
99};
100
101/* The system is simple enough that only one state is required, each command
102 * function will perform it's own sanity checking to solidly simulate states.
103 */
104static int (*state_all[])(clientenv *) = {
105 all_noop, /* NOOP */
106 all_rset, /* RSET */
107 all_quit, /* QUIT */
108 all_help, /* HELP */
109 all_helo, /* HELO */
110 all_mail, /* MAIL */
111 all_rcpt, /* RCPT */
112 all_data, /* DATA */
113 all_beer /* BEER */
114};
115
116/* The definitions of our many various states (-: */
117static struct state states[] = {
118 {state_all, 0, "Abnormal error"}
119};
120
121/* Used for mmsyslog() */
122static int LOGLEVEL;
123
124/* Used for clenv allocation buffering */
125static list *clist;
126static pth_mutex_t clist_lock;
127
128/* Used for flood protection cache */
129static list *mlist;
130static pth_mutex_t mlist_lock;
131
132/* Used for rcpt allocation buffering */
133static list *rlist;
134static pth_mutex_t rlist_lock;
135
5eb34fba
MM
136/* Pool used to optimize creatiing/destroying mmfd mutexes */
137static pth_mutex_t mutexes_lock;
138static list *mutexes;
139
47071c2b
MM
140/* Global bandwidth shaping fdb context */
141static fdbcontext fdbc;
142
143/* Quick index to RCPT command replies (see mmsmtpd.h for matching defines) */
144static struct reply_messages rcpt_msg[] = {
145 {250, "Recipient ok"},
146 {503, "Use MAIL first"},
147 {552, "Too many recipients"},
148 {501, "Invalid address"},
149 {250, "Recipient already added"},
150 {402, "Mailbox full, try again later"},
151 {402, "Rate exceeded, try again later"},
152 {452, "Internal error, contact administrator"}
153};
154
155/* Fast index to DATA replies (see headerfile for matching keywords) */
156static struct reply_messages data_msg[] = {
157 {354, "Submit message ending with a single ."},
158 {250, "Ok, mail delivered"},
159 {552, "Too much mail data"},
160 {552, "Too many hops"},
161 {452, "Internal error"}
162};
163
5eb34fba
MM
164/* Pth support for mmfd library (that library rocks my world :) */
165static fdfuncs gfdf = {
166 malloc,
167 free,
168 pth_poll,
169 pth_read,
170 pth_write,
171 pth_sleep,
172 pth_usleep,
173 _pth_mutex_create,
174 _pth_mutex_destroy,
175 _pth_mutex_lock,
176 _pth_mutex_unlock,
177 _pth_thread_yield
178};
179
47071c2b
MM
180
181
182
183/* MAIN */
184
185int
186main(int argc, char **argv)
187{
188 uid_t uid;
189 gid_t *gids;
190 char *conf_file = "/etc/mmsmtpd.conf";
191 int ngids, ret = -1;
192 long facility;
193 char *db_host;
194 bool strlist;
195 CRES cres;
196 CARG *cargp;
197 CARG cargs[] = {
198 {CAT_STR, 1, 255, CAS_UNTOUCHED, "CHROOT_DIR", CONF.CHROOT_DIR},
199 {CAT_STR, 1, 255, CAS_UNTOUCHED, "PID_PATH", CONF.PID_PATH},
200 {CAT_STR, 1, 31, CAS_UNTOUCHED, "USER", CONF.USER},
201 {CAT_STR, 1, 255, CAS_UNTOUCHED, "GROUPS", CONF.GROUPS},
202 {CAT_STR, 1, 31, CAS_UNTOUCHED, "LOG_FACILITY", CONF.LOG_FACILITY},
203 {CAT_STR, 1, 1023, CAS_UNTOUCHED, "SERVER_NAMES",
204 CONF.SERVER_NAMES},
205 {CAT_STR, 1, 1023, CAS_UNTOUCHED, "LISTEN_IPS", CONF.LISTEN_IPS},
206 {CAT_STR, 1, 63, CAS_UNTOUCHED, "DB_HOST", CONF.DB_HOST},
207 {CAT_STR, 1, 31, CAS_UNTOUCHED, "DB_USER", CONF.DB_USER},
208 {CAT_STR, 1, 31, CAS_UNTOUCHED, "DB_PASSWORD", CONF.DB_PASSWORD},
209 {CAT_STR, 1, 31, CAS_UNTOUCHED, "DB_DATABASE", CONF.DB_DATABASE},
210 {CAT_VAL, 1, 32, CAS_UNTOUCHED, "ASYNC_PROCESSES",
211 &CONF.ASYNC_PROCESSES},
212 {CAT_VAL, 1, 9999, CAS_UNTOUCHED, "ALLOC_BUFFERS",
213 &CONF.ALLOC_BUFFERS},
214 {CAT_VAL, 0, 4, CAS_UNTOUCHED, "LOG_LEVEL", &CONF.LOG_LEVEL},
215 {CAT_VAL, 1, 65535, CAS_UNTOUCHED, "LISTEN_PORT",
216 &CONF.LISTEN_PORT},
217 {CAT_VAL, 1, 1000, CAS_UNTOUCHED, "MAX_ERRORS", &CONF.MAX_ERRORS},
218 {CAT_VAL, 1, 99999, CAS_UNTOUCHED, "MAX_IPS", &CONF.MAX_IPS},
219 {CAT_VAL, 1, 99999, CAS_UNTOUCHED, "MAX_PER_IP", &CONF.MAX_PER_IP},
220 {CAT_VAL, 0, 99999, CAS_UNTOUCHED, "CONNECTION_RATE",
221 &CONF.CONNECTION_RATE},
222 {CAT_VAL, 0, 99999, CAS_UNTOUCHED, "CONNECTION_PERIOD",
223 &CONF.CONNECTION_PERIOD},
224 {CAT_VAL, 1, 99999, CAS_UNTOUCHED, "INPUT_TIMEOUT",
225 &CONF.INPUT_TIMEOUT},
226 {CAT_VAL, 0, 99999, CAS_UNTOUCHED, "BANDWIDTH_IN",
227 &CONF.BANDWIDTH_IN},
228 {CAT_VAL, 0, 99999, CAS_UNTOUCHED, "BANDWIDTH_OUT",
229 &CONF.BANDWIDTH_OUT},
230 {CAT_VAL, 0, 99999, CAS_UNTOUCHED, "GBANDWIDTH_IN",
231 &CONF.GBANDWIDTH_IN},
232 {CAT_VAL, 0, 99999, CAS_UNTOUCHED, "GBANDWIDTH_OUT",
233 &CONF.GBANDWIDTH_OUT},
234 {CAT_VAL, 1, 99999, CAS_UNTOUCHED, "MAX_RCPTS", &CONF.MAX_RCPTS},
235 {CAT_VAL, 1, 999999, CAS_UNTOUCHED, "MAX_DATA_LINES",
236 &CONF.MAX_DATA_LINES},
237 {CAT_VAL, 1, 99999999, CAS_UNTOUCHED, "MAX_DATA_SIZE",
238 &CONF.MAX_DATA_SIZE},
239 {CAT_VAL, 1, 999, CAS_UNTOUCHED, "MAX_HOPS", &CONF.MAX_HOPS},
240 {CAT_VAL, 1, 999999, CAS_UNTOUCHED, "FLOOD_MESSAGES",
241 &CONF.FLOOD_MESSAGES},
242 {CAT_VAL, 1, 120, CAS_UNTOUCHED, "FLOOD_EXPIRES",
243 &CONF.FLOOD_EXPIRES},
244 {CAT_VAL, 50, 999999, CAS_UNTOUCHED, "FLOOD_CACHE",
245 &CONF.FLOOD_CACHE},
246 {CAT_BOOL, 0, 0, CAS_UNTOUCHED, "RESOLVE_HOSTS",
247 &CONF.RESOLVE_HOSTS},
46cf2cd5
MM
248 {CAT_BOOL, 0, 0, CAS_UNTOUCHED, "RESOLVE_HELO",
249 &CONF.RESOLVE_HELO},
47071c2b
MM
250 {CAT_BOOL, 0, 0, CAS_UNTOUCHED, "RESOLVE_MX_MAIL",
251 &CONF.RESOLVE_MX_MAIL},
252 {CAT_BOOL, 0, 0, CAS_UNTOUCHED, "REQUIRE_HELO",
253 &CONF.REQUIRE_HELO},
254 {CAT_BOOL, 0, 0, CAS_UNTOUCHED, "FLOOD_PROTECTION",
255 &CONF.FLOOD_PROTECTION},
256 {CAT_BOOL, 0, 0, CAS_UNTOUCHED, "STATFAIL_ADDRESS",
257 &CONF.STATFAIL_ADDRESS},
258 {CAT_BOOL, 0, 0, CAS_UNTOUCHED, "STATFAIL_FLOOD",
259 &CONF.STATFAIL_FLOOD},
260 {CAT_BOOL, 0, 0, CAS_UNTOUCHED, "STATFAIL_FULL",
261 &CONF.STATFAIL_FULL},
262 {CAT_BOOL, 0, 0, CAS_UNTOUCHED, "STATFAIL_TIMEOUT",
263 &CONF.STATFAIL_TIMEOUT},
e334174e
MM
264 {CAT_BOOL, 0, 0, CAS_UNTOUCHED, "DELAY_ON_ERROR",
265 &CONF.DELAY_ON_ERROR},
47071c2b
MM
266 {CAT_END, 0, 0, 0, NULL, NULL}
267 };
268 CMAP cmap[] = {
269 {"LOG_AUTH", LOG_AUTH},
270 {"LOG_AUTHPRIV", LOG_AUTHPRIV},
271 {"LOG_CRON", LOG_CRON},
272 {"LOG_DAEMON", LOG_DAEMON},
273 {"LOG_FTP", LOG_FTP},
274 {"LOG_LPR", LOG_LPR},
275 {"LOG_MAIL", LOG_MAIL},
276 {"LOG_NEWS", LOG_NEWS},
277 {"LOG_SYSLOG", LOG_SYSLOG},
278 {"LOG_USER", LOG_USER},
279 {"LOG_UUCP", LOG_UUCP},
280 {NULL, 0}
281 };
282 struct async_func afuncs[] = {
283 {async_resquery, sizeof(struct async_resquery_msg)},
284 {NULL, 0}
285 };
286
287 /* Set defaults */
288 *CONF.CHROOT_DIR = 0;
289 mm_strcpy(CONF.PID_PATH, "/var/run/mmsmtpd.pid");
290 mm_strcpy(CONF.USER, "mmmail");
291 mm_strcpy(CONF.GROUPS, "mmmail mmstat");
292 mm_strcpy(CONF.LOG_FACILITY, "LOG_AUTHPRIV");
293 mm_strcpy(CONF.SERVER_NAMES, "smtp.localhost");
294 mm_strcpy(CONF.LISTEN_IPS, "127.0.0.1");
295 mm_strcpy(CONF.DB_HOST, "localhost");
296 mm_strcpy(CONF.DB_USER, "mmmail");
297 mm_strcpy(CONF.DB_PASSWORD, "mmmailpassword");
298 mm_strcpy(CONF.DB_DATABASE, "mmmail");
299 CONF.ASYNC_PROCESSES = 3;
300 CONF.ALLOC_BUFFERS = 1;
301 CONF.LOG_LEVEL = 3;
302 CONF.LISTEN_PORT = 25;
303 CONF.MAX_ERRORS = 16;
304 CONF.MAX_IPS = 64;
305 CONF.MAX_PER_IP = 1;
306 CONF.CONNECTION_RATE = 10;
307 CONF.CONNECTION_PERIOD = 30;
308 CONF.INPUT_TIMEOUT = 900;
309 CONF.BANDWIDTH_IN = 16;
310 CONF.BANDWIDTH_OUT = 4;
311 CONF.GBANDWIDTH_IN = 0;
312 CONF.GBANDWIDTH_OUT = 0;
313 CONF.MAX_RCPTS = 16;
314 CONF.MAX_DATA_LINES = 15000;
315 CONF.MAX_DATA_SIZE = 1048576;
316 CONF.MAX_HOPS = 30;
317 CONF.FLOOD_MESSAGES = 20;
318 CONF.FLOOD_EXPIRES = 30;
319 CONF.FLOOD_CACHE = 100;
320 CONF.RESOLVE_HOSTS = FALSE;
46cf2cd5 321 CONF.RESOLVE_HELO = FALSE;
47071c2b
MM
322 CONF.RESOLVE_MX_MAIL = FALSE;
323 CONF.REQUIRE_HELO = FALSE;
324 CONF.FLOOD_PROTECTION = TRUE;
325 CONF.STATFAIL_ADDRESS = TRUE;
326 CONF.STATFAIL_FLOOD = TRUE;
327 CONF.STATFAIL_FULL = TRUE;
328 CONF.STATFAIL_TIMEOUT = TRUE;
e334174e 329 CONF.DELAY_ON_ERROR = FALSE;
47071c2b
MM
330
331 /* Advertize */
332 printf("\r\n+++ %s (%s)\r\n\r\n", DAEMON_NAME, DAEMON_VERSION);
333
334 /* Read config file */
335 if (argc == 2)
336 conf_file = argv[1];
337 if (!mmreadcfg(conf_file, cargs, &cres)) {
338 /* Error parsing configuration file, report which */
339 printf("\nError parsing '%s'\n", conf_file);
340 printf("Error : %s\n", mmreadcfg_strerr(cres.CR_Err));
341 if (*(cres.CR_Data)) printf("Data : %s\n", cres.CR_Data);
342 if (cres.CR_Number != -1) {
343 cargp = &cargs[cres.CR_Number];
344 printf("Keyword: %s\n", cargp->CA_KW);
345 printf("Minimum: %ld\n", cargp->CA_Min);
346 printf("Maximum: %ld\n", cargp->CA_Max);
347 }
348 printf("\n");
349 exit(-1);
350 }
351
352 /* Post parsing */
353 if (!mmmapstring(cmap, CONF.LOG_FACILITY, &facility)) {
354 printf("\nUnknown syslog facility %s\n\n", CONF.LOG_FACILITY);
355 exit(-1);
356 }
357 LOGLEVEL = CONF.LOG_LEVEL;
358 /* Translate to numbers the user and group we were told to run as */
359 if ((uid = mmgetuid(CONF.USER)) == -1) {
360 printf("\nUnknown user '%s'\n\n", CONF.USER);
361 exit(-1);
362 }
363 if (!(gids = mmgetgidarray(&ngids, CONF.GROUPS)) == -1) {
364 printf("\nOne of following groups unknown: '%s'\n\n", CONF.GROUPS);
365 exit(-1);
366 }
367 if (!(mm_strcmp(CONF.DB_HOST, "localhost"))) db_host = NULL;
368 else db_host = CONF.DB_HOST;
369
370 /* Finally init everything */
371 openlog(DAEMON_NAME, LOG_PID | LOG_NDELAY, facility);
372
373#ifndef NODROPPRIVS
374 if ((getuid())) {
375 printf("\nOnly the super user may start this daemon\n\n");
376 mmsyslog(0, LOGLEVEL, "* Only superuser can start me");
377 exit(-1);
378 }
379#else /* NODROPPRIVS */
380 if ((getuid()) == 0) {
381 printf("\nCompiled with NODROPPRIVS, refusing to run as uid 0\n\n");
382 mmsyslog(0, LOGLEVEL, "* NODROPPRIVS, refusing to run as uid 0");
383 exit(-1);
384 }
385#endif /* !NODROPPRIVS */
386
387 mmstat_initialize(); /* Required in case we chroot(2) */
388 res_init();
389
390 make_daemon(CONF.PID_PATH, CONF.CHROOT_DIR);
391 async_init(afuncs, CONF.ASYNC_PROCESSES, uid, gids, ngids);
392 packcommands(commands, 4);
393
394 pth_init();
395 async_init_pth();
396 pth_mutex_init(&clist_lock);
397 pth_mutex_init(&mlist_lock);
398 pth_mutex_init(&rlist_lock);
5eb34fba 399 pth_mutex_init(&mutexes_lock);
47071c2b
MM
400
401 /* Allocate necessary pools */
5eb34fba 402 /* Client nodes */
47071c2b
MM
403 clist = openlist(malloc, free, sizeof(clientenv),
404 16384 * CONF.ALLOC_BUFFERS, 0);
405 /* RCPT nodes */
406 rlist = openlist(malloc, free, sizeof(rcptnode),
407 16384 * CONF.ALLOC_BUFFERS, 0);
5eb34fba
MM
408 /* Mutexes pool for mmfd */
409 mutexes = openlist(malloc, free, sizeof(struct mutexnode),
410 16386 * CONF.ALLOC_BUFFERS, 0);
47071c2b
MM
411 /* Rate nodes */
412 if (CONF.FLOOD_PROTECTION)
413 mlist = blocklist(malloc, free, sizeof(mnode), CONF.FLOOD_CACHE);
414 else mlist = NULL;
415 /* mmstr nodes */
416 strlist = mmstrinit(malloc, free, 65536 * CONF.ALLOC_BUFFERS, 0);
417
5eb34fba 418 if (clist && rlist && mutexes && strlist &&
47071c2b 419 (!CONF.FLOOD_PROTECTION || mlist)) {
5eb34fba
MM
420 fdbcinit(&gfdf, &fdbc, CONF.GBANDWIDTH_IN * 1024,
421 CONF.GBANDWIDTH_OUT * 1024);
47071c2b
MM
422 if ((mmsql_open(db_host, CONF.DB_USER, CONF.DB_PASSWORD,
423 CONF.DB_DATABASE))) {
424
425 tcp_server("402 Server too busy, try again\r\n",
426 CONF.SERVER_NAMES, CONF.LISTEN_IPS, uid, gids, ngids,
427 CONF.MAX_IPS, CONF.MAX_PER_IP, CONF.CONNECTION_RATE,
428 CONF.CONNECTION_PERIOD, CONF.INPUT_TIMEOUT,
429 CONF.LISTEN_PORT, CONF.RESOLVE_HOSTS, handleclient);
430
431 mmfreegidarray(gids);
432 ret = 0;
433 mmsql_close();
434 } else
435 printf("\nCannot connect to mysqld\n\n");
436 } else
437 printf("\nOut of memory\n\n");
438
439 if (strlist) mmstrexit();
5eb34fba 440 if (mutexes) closelist(mutexes);
47071c2b
MM
441 if (mlist) closelist(mlist);
442 if (rlist) closelist(rlist);
443 if (clist) closelist(clist);
444
445 fdbcdestroy(&fdbc);
42dd3825 446 kill(0, SIGTERM);
47071c2b
MM
447 exit(ret);
448}
449
450
451/* Here consists of our command functions */
452
453
454static int
455all_noop(clientenv *clenv)
456{
5eb34fba 457 reply(clenv->fdb, 250, FALSE, "Ok, nothing performed");
47071c2b 458
5eb34fba 459 return (STATE_CURRENT);
47071c2b
MM
460}
461
462
463static int
464all_rset(clientenv *clenv)
465{
466 int nextstate = STATE_CURRENT;
467 fdbuf *fdb = clenv->fdb;
47071c2b
MM
468
469 if (clenv->buffer[4] == 0) {
5eb34fba 470 if (!init_clientenv(clenv, TRUE))
47071c2b 471 nextstate = STATE_ERROR;
5eb34fba
MM
472 else
473 reply(fdb, 250, FALSE, "Reset state");
474 } else {
475 reply(fdb, 550, FALSE, "Command syntax error");
e334174e 476 REGISTER_ERROR(clenv);
47071c2b
MM
477 }
478
479 return (nextstate);
480}
481
482
483static int
484all_quit(clientenv *clenv)
485{
5eb34fba
MM
486 reply(clenv->fdb, 221, FALSE, "%s closing connection",
487 clenv->iface->hostname);
47071c2b 488
5eb34fba 489 return (STATE_END);
47071c2b
MM
490}
491
492
493static int
494all_help(clientenv *clenv)
495{
5eb34fba 496 int col;
47071c2b
MM
497 fdbuf *fdb = clenv->fdb;
498 char *args[3], *cmdline = clenv->buffer, *tmp;
499
500 /* First check if a topic was specified */
501 if ((col = mm_straspl(args, cmdline, 2)) == 2) {
5eb34fba 502 register int i = 0;
47071c2b
MM
503 register int32_t chash;
504 register bool valid;
505
506 /* Help requested on a topic */
507 valid = FALSE;
508 if ((chash = mm_strpack32(args[1], 4)) != -1) {
509 for (i = 0; commands[i].name; i++) {
510 if (commands[i].hash == chash) {
511 valid = TRUE;
512 break;
513 }
514 }
515 }
516
517 col = 0;
518 if (valid) {
5eb34fba
MM
519 reply(fdb, 214, TRUE, commands[i].args);
520 reply(fdb, 214, TRUE, " %s", commands[i].desc);
47071c2b
MM
521 col = 2;
522 }
523
5eb34fba
MM
524 if (col)
525 reply(fdb, 214, FALSE, "End of HELP information");
526 else {
527 reply(fdb, 504, FALSE, "Unknown HELP topic");
e334174e 528 REGISTER_ERROR(clenv);
47071c2b
MM
529 }
530
531 } else {
532 register int i;
533
534 /* No, display the topics */
5eb34fba
MM
535 reply(fdb, 214, TRUE, "Available topics:");
536 fdbwrite(fdb, "214-", 4);
537 col = 1;
538 for (i = 0; (tmp = commands[i].name); i++) {
539 if (commands[i].desc) {
540 if (col == 0) fdbwrite(fdb, "\r\n214-", 6);
541 col++;
542 if (col > 4) col = 0;
543 fdbprintf(fdb, " %s", tmp);
47071c2b 544 }
47071c2b 545 }
5eb34fba 546 fdbwrite(fdb, "\r\n", 2);
47071c2b 547
5eb34fba
MM
548 reply(fdb, 214, TRUE, "For more information, use HELP <topic>");
549 reply(fdb, 214, FALSE, "End of HELP information");
47071c2b
MM
550 }
551
5eb34fba 552 return (STATE_CURRENT);
47071c2b
MM
553}
554
555
556static int
557all_helo(clientenv *clenv)
558{
47071c2b
MM
559 fdbuf *fdb = clenv->fdb;
560 char *args[3], *cmdline = clenv->buffer;
561
562 if ((mm_straspl(args, cmdline, 2)) == 2) {
563 if (!clenv->helo) {
46cf2cd5
MM
564 if (valid_host(clenv, args[1],
565 CONF.RESOLVE_HELO ? HOST_RES : HOST_NORES, TRUE)) {
5eb34fba
MM
566 if ((clenv->helo = mmstrdup(args[1])) == NULL)
567 mmsyslog(0, LOGLEVEL, "all_helo() - * Out of memory!");
568 reply(fdb, 250, FALSE, "%s ok", clenv->iface->hostname);
47071c2b 569 } else {
5eb34fba 570 reply(fdb, 501, FALSE, "Invalid hostname");
e334174e 571 REGISTER_ERROR(clenv);
47071c2b
MM
572 }
573 } else {
5eb34fba 574 reply(fdb, 503, FALSE, "Duplicate HELO, use RSET or proceed");
e334174e 575 REGISTER_ERROR(clenv);
47071c2b
MM
576 }
577 } else {
5eb34fba 578 reply(fdb, 550, FALSE, "Command syntax error");
e334174e 579 REGISTER_ERROR(clenv);
47071c2b
MM
580 }
581
5eb34fba 582 return (STATE_CURRENT);
47071c2b
MM
583}
584
585
586static int
587all_mail(clientenv *clenv)
588{
589 int nextstate = STATE_CURRENT;
590 fdbuf *fdb = clenv->fdb;
591 char addr[64];
592 bool valid;
593
594 if (!CONF.REQUIRE_HELO || clenv->helo) {
595
596 if (!clenv->from) {
597
598 valid = FALSE;
599 if (!(mm_strnicmp(" FROM:<>", &clenv->buffer[4], 8))) {
600 /* Some systems use empty MAIL FROM like this, make sure
601 * that IP address or hostname is allowed to do this.
602 */
603 if (clenv->c_hostname) valid = check_nofrom(clenv->c_hostname);
604 else valid = check_nofrom(clenv->c_ipaddr);
605 if (valid) *addr = 0;
606 } else
607 valid = valid_address(clenv, addr, clenv->buffer,
46cf2cd5 608 (CONF.RESOLVE_MX_MAIL) ? HOST_RES_MX : HOST_NORES);
47071c2b
MM
609
610 if (valid) {
5eb34fba
MM
611 if ((clenv->from = (char *)mmstrdup(addr)))
612 reply(fdb, 250, FALSE, "Sender ok");
613 else
47071c2b
MM
614 nextstate = STATE_ERROR;
615 } else {
5eb34fba 616 reply(fdb, 501, FALSE, "Invalid address");
e334174e 617 REGISTER_ERROR(clenv);
47071c2b
MM
618 }
619
620 } else {
5eb34fba 621 reply(fdb, 503, FALSE, "Sender already specified");
e334174e 622 REGISTER_ERROR(clenv);
47071c2b
MM
623 }
624
625 } else {
5eb34fba 626 reply(fdb, 503, FALSE, "Use HELO first");
e334174e 627 REGISTER_ERROR(clenv);
47071c2b
MM
628 }
629
630 return (nextstate);
631}
632
633
634static int
635all_rcpt(clientenv *clenv)
636{
637 int nextstate = STATE_CURRENT;
638 fdbuf *fdb = clenv->fdb;
639 char addr[64], foraddr[64], *line = clenv->buffer;
640 int reason;
641 bool valid;
642 long max_size, size, max_msgs, msgs;
643 u_int64_t ahash;
644
645 /* I have opted for an elimination process here as there are many cases
646 * which can cause an RCPT to be refused, and alot of indenting was to
647 * be avoided for clarity. Functions could also be used but it has not
648 * been necessary this far, and we want the code performance to be optimal.
649 */
650 valid = TRUE;
651 reason = RCPT_OK;
652
653 if (!clenv->from) {
654 valid = FALSE;
655 reason = RCPT_NOFROM;
656 }
657
658 /* First make sure to not allow more RCPTs than CONF.MAX_RCPTS */
659 if (valid) {
660 if (!(clenv->rcpt.nodes < CONF.MAX_RCPTS)) {
661 valid = FALSE;
662 reason = RCPT_MAX;
663 if (CONF.STATFAIL_FLOOD)
664 mmstat(&clenv->pstat, STAT_UPDATE, 1,
665 "mmsmtpd.failed.flood.%s", clenv->c_ipaddr);
666 }
667 }
668
669 /* Verify if existing address, if it isn't verify for any alias
670 * matching it and of course check for address validity again for
671 * safety. This way we make sure that an alias pattern does not over-
672 * ride an existing address, and that we only archive a message into
673 * an existing mailbox.
674 */
675 if (valid) {
676 valid = FALSE;
46cf2cd5 677 if (valid_address(clenv, addr, line, HOST_NORES)) {
47071c2b
MM
678 mm_strcpy(foraddr, addr);
679 valid = local_address(addr, &max_size, &size, &max_msgs, &msgs);
680 if (!valid) {
681 if (check_alias(addr))
682 valid = local_address(addr, &max_size, &size, &max_msgs,
683 &msgs);
684 }
685 }
686 if (!valid) {
687 reason = RCPT_INVALID;
688 if (CONF.STATFAIL_ADDRESS)
689 mmstat(&clenv->pstat, STAT_UPDATE, 1,
690 "mmsmtpd.failed.address.%s", clenv->c_ipaddr);
691 }
692 }
693
694 /* Make sure mailbox quota limits are respected */
695 if (valid) {
696 if (!((size <= max_size) && (msgs <= max_msgs))) {
697 mmsyslog(0, LOGLEVEL, "%s mailbox full (%ld,%ld %ld,%ld)",
698 addr, max_size, size, max_msgs, msgs);
699 valid = FALSE;
700 reason = RCPT_FULL;
701 if (CONF.STATFAIL_FULL)
702 mmstat(&clenv->pstat, STAT_UPDATE, 1,
703 "mmsmtpd.failed.full.%s", addr);
704 }
705 }
706
707 /* Make sure that we only allow one RCPT per mailbox (alias already
708 * redirected to it)
709 */
710 if (valid) {
711 register rcptnode *rnode;
712 register int cnt;
713
714 ahash = hashstr64(addr);
715 cnt = 0;
716 rnode = (rcptnode *)clenv->rcpt.top;
717 while (rnode) {
718 if (rnode->hash == ahash) {
719 valid = FALSE;
720 reason = RCPT_EXISTS;
721 break;
722 }
723 cnt++;
724 if (cnt > 64) {
725 cnt = 0;
726 pth_yield(NULL);
727 }
728 rnode = (rcptnode *)rnode->nod.next;
729 }
730 }
731
732 /* If CONF.FLOOD_PROTECTION is on, make sure that we respect the rate
733 * of CONF.FLOOD_MESSAGES within CONF.FLOOD_EXPIRES for this client
734 */
735 if (valid && mlist != NULL) {
736 register mnode *mnod, *next;
737 register int cnt;
738 register u_int64_t hash;
739 register time_t t;
740
741 cnt = 0;
742 if (clenv->c_hostname) hash = hashstr64(clenv->c_hostname);
743 else hash = hashstr64(clenv->c_ipaddr);
744 t = time(NULL);
745
746 pth_mutex_acquire(&mlist_lock, FALSE, NULL);
747 /* First acquire our mnode, or create it if required */
748 mnod = (mnode *)mlist->top;
749 while (mnod) {
750 next = (mnode *)mnod->nod.next;
751 if (mnod->expires < t) {
752 /* This entry has expired, expunge it */
5eb34fba 753 UNLINKNODE(mlist, (node *)mnod);
47071c2b
MM
754 freenode((node *)mnod);
755 } else if (mnod->hash == hash) break;
756 /* Of course as we still are holding the mutex another
757 * thread in the same state would wait still...
758 */
759 cnt++;
760 if (cnt > 64) {
761 cnt = 0;
762 pth_yield(NULL);
763 }
764 mnod = next;
765 }
766
767 if (!mnod) {
768 /* Create a new mnode since none matched */
769 if ((mnod = (mnode *)allocnode(mlist, FALSE)) != NULL) {
770 mnod->hash = hash;
771 mnod->expires = t + (CONF.FLOOD_EXPIRES * 60);
772 mnod->posts = 1;
5eb34fba 773 APPENDNODE(mlist, (node *)mnod);
47071c2b
MM
774 } else {
775 valid = FALSE;
776 reason = RCPT_FLOOD;
777 mmsyslog(0, LOGLEVEL, "* FLOOD_CACHE not large enough");
778 }
779 } else {
780 /* We found a cached entry for this client,
781 * check limits and update.
782 */
783 mnod->posts++;
784 if (mnod->posts > CONF.FLOOD_MESSAGES) {
785 valid = FALSE;
786 reason = RCPT_FLOOD;
787 mmsyslog(0, LOGLEVEL,
788 "%08X Considered flood and rejected (%ld message(s) \
789within last %ld minute(s))", clenv->id, mnod->posts,
790 CONF.FLOOD_EXPIRES);
791 }
792 }
793 pth_mutex_release(&mlist_lock);
794
795 if (!valid && CONF.STATFAIL_FLOOD)
796 mmstat(&clenv->pstat, STAT_UPDATE, 1,
797 "mmsmtpd.failed.flood.%s", clenv->c_ipaddr);
798 }
799
800 /* Finally append new RCPT to list */
801 if (valid) {
802 register rcptnode *rnode;
803
804 reason = RCPT_ERROR;
805 pth_mutex_acquire(&rlist_lock, FALSE, NULL);
806 rnode = (rcptnode *)allocnode(rlist, FALSE);
807 pth_mutex_release(&rlist_lock);
808 if (rnode) {
809 mm_strcpy(rnode->address, addr);
810 mm_strcpy(rnode->foraddress, foraddr);
811 rnode->hash = ahash;
5eb34fba 812 APPENDNODE(&clenv->rcpt, (node *)rnode);
47071c2b
MM
813 reason = RCPT_OK;
814 clenv->rcpts++;
815 } else {
816 mmsyslog(0, LOGLEVEL,
817 "%08X * all_rcpt() - Error allocating new rcpt node",
818 clenv->id);
819 nextstate = STATE_ERROR;
820 }
821 }
822
823 /* Reply with appropriate message */
824 if (reason != RCPT_OK)
e334174e 825 REGISTER_ERROR(clenv);
5eb34fba 826 reply(fdb, rcpt_msg[reason].code, FALSE, rcpt_msg[reason].msg);
47071c2b
MM
827
828 return (nextstate);
829}
830
831
832static int
833all_data(clientenv *clenv)
834{
835 int nextstate = STATE_CURRENT;
836 fdbuf *fdb = clenv->fdb;
837
838 if (clenv->buffer[4] == 0) {
839 if (clenv->from) {
840 if (clenv->rcpt.nodes) {
841 if (!do_data(clenv))
842 nextstate = STATE_ERROR;
843 else
844 clenv->messages++;
845 } else {
5eb34fba 846 reply(fdb, 502, FALSE, "Use RCPT first");
e334174e 847 REGISTER_ERROR(clenv);
47071c2b
MM
848 }
849 } else {
5eb34fba 850 reply(fdb, 503, FALSE, "Use MAIL and RCPT first");
e334174e 851 REGISTER_ERROR(clenv);
47071c2b
MM
852 }
853 } else {
5eb34fba 854 reply(fdb, 550, FALSE, "Command syntax error");
e334174e 855 REGISTER_ERROR(clenv);
47071c2b
MM
856 }
857
858 return (nextstate);
859}
860
861
862static int
863all_beer(clientenv *clenv)
864{
5eb34fba 865 reply(clenv->fdb, 420, FALSE, "Here, enjoy!");
47071c2b 866
5eb34fba 867 return (STATE_CURRENT);
47071c2b
MM
868}
869
870
871
872
873/* Used to initialize command table hashes */
874static void
875packcommands(struct command *cmd, size_t min)
876{
877 while (cmd->name) {
878 cmd->hash = mm_strpack32(cmd->name, min);
879 cmd++;
880 }
881}
882
883
884/* Function used to return standard RFC result strings to the client,
5eb34fba
MM
885 * and returns FALSE on error.
886 * NOTE: As we are no longer calling write() directly, but fdbprintf()
887 * buffering function instead, it is no longer necessary to check the reply()
888 * return value each time it is called. We made sure to ignore SIGPIPE,
889 * and we let the main state switcher loop check connection state via
890 * fdbgets().
47071c2b
MM
891 */
892static bool
893reply(fdbuf *fdb, int code, bool cont, const char *fmt, ...)
894{
895 char buf[1024];
896 va_list arg_ptr;
897 bool err = TRUE;
898
899 *buf = 0;
900 va_start(arg_ptr, fmt);
901 vsnprintf(buf, 1023, fmt, arg_ptr);
902 va_end(arg_ptr);
903
904 if (cont) err = fdbprintf(fdb, "%d-%s\r\n", code, buf);
905 else err = fdbprintf(fdb, "%d %s\r\n", code, buf);
906
907 mmsyslog(3, LOGLEVEL, "> %d (%s)", code, buf);
908
909 return (err);
910}
911
912
913/* Allocate and prepare a clenv. Returns NULL on error */
914static clientenv *
915alloc_clientenv(void)
916{
917 clientenv *clenv;
918
919 pth_mutex_acquire(&clist_lock, FALSE, NULL);
920 clenv = (clientenv *)allocnode(clist, TRUE);
921 pth_mutex_release(&clist_lock);
922
923 if (clenv) {
5eb34fba
MM
924 mmstat_init(&clenv->vstat, FALSE, TRUE);
925 mmstat_init(&clenv->pstat, TRUE, FALSE);
47071c2b
MM
926 }
927
928 return (clenv);
929}
930
931
932/* Useful on RSET to reset initial clenv state,
933 * returns TRUE on success or FALSE on error
934 */
935static bool
936init_clientenv(clientenv *clenv, bool helo)
937{
938 if (helo && clenv->helo) clenv->helo = mmstrfree(clenv->helo);
939 if (clenv->from) clenv->from = mmstrfree(clenv->from);
940 empty_rcpts(&clenv->rcpt);
941
942 return (TRUE);
943}
944
945
946/* Frees all ressources allocated by a clenv */
947static clientenv *
948free_clientenv(clientenv *clenv)
949{
950 if (clenv->helo) mmstrfree(clenv->helo);
951 if (clenv->from) mmstrfree(clenv->from);
952 empty_rcpts(&clenv->rcpt);
953
954 pth_mutex_acquire(&clist_lock, FALSE, NULL);
955 freenode((node *)clenv);
956 pth_mutex_release(&clist_lock);
957
958 return (NULL);
959}
960
961
962/* Useful to free all rcpts for a clientenv */
963static void
964empty_rcpts(list *lst)
965{
966 node *nod;
967
968 pth_mutex_acquire(&rlist_lock, FALSE, NULL);
969 while ((nod = lst->top)) {
5eb34fba 970 UNLINKNODE(lst, nod);
47071c2b
MM
971 freenode(nod);
972 }
973 pth_mutex_release(&rlist_lock);
974}
975
976
977/* Checks in the list of aliases for any pattern matching the address, and
978 * map it to the real address to redirect to, replacing supplied address.
979 * The addr char array must at least be 64 bytes. Returns FALSE if no alias
980 * exist for the address, or TRUE on success.
ec182166
MM
981 * XXX Could possibly use an async function, if we allow the async processes
982 * to connect to MySQLd.
47071c2b
MM
983 */
984static bool
985check_alias(char *addr)
986{
987 bool res = FALSE;
46cf2cd5 988 char *args[3], oaddr[64], query[1024];
42dd3825
MM
989
990 if ((mm_strspl(args, addr, 2, '@')) == 2) {
991 MYSQL_RES *mysqlres;
992
46cf2cd5 993 mm_strncpy(oaddr, args[0], 63);
42dd3825
MM
994 snprintf(query, 1024, "SELECT alias_pattern,alias_box FROM alias \
995WHERE alias_domain='%s'", args[1]);
996 if ((mysqlres = mmsql_query(query, mm_strlen(query))) != NULL) {
997 if ((mysql_num_rows(mysqlres)) > 0) {
998 char pat[64];
999 int cur = 0, max = -1, cnt = 0;
1000 MYSQL_ROW *row;
1001 unsigned long *lengths;
1002
1003 /* Find best match */
1004 while ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres))
1005 != NULL) {
47071c2b
MM
1006 lengths = mysql_fetch_lengths(mysqlres);
1007 if (row[0] && row[1]) {
1008 mm_memcpy(pat, row[0], lengths[0]);
1009 pat[lengths[0]] = 0;
46cf2cd5 1010 if ((cur = alias_match(oaddr, pat)) != -1) {
42dd3825
MM
1011 if (cur > max) {
1012 /* Matches better, remember this one */
42dd3825
MM
1013 max = cur;
1014 mm_memcpy(addr, row[1], lengths[1]);
1015 addr[lengths[1]] = 0;
42dd3825 1016 }
47071c2b
MM
1017 }
1018 }
42dd3825
MM
1019 cnt++;
1020 if (cnt > 64) {
1021 cnt = 0;
1022 pth_yield(NULL);
1023 }
1024 }
1025 if (max > -1)
1026 res = TRUE;
1027 else
1028 /* Restore old address we have destroyed */
1029 args[1][-1] = '@';
47071c2b 1030 }
42dd3825 1031 mysqlres = mmsql_free_result(mysqlres);
47071c2b 1032 }
42dd3825 1033
47071c2b
MM
1034 }
1035
1036 return (res);
1037}
1038
1039
1040static bool
1041check_nofrom(const char *addr)
1042{
1043 bool res = FALSE;
47071c2b 1044 MYSQL_RES *mysqlres;
47071c2b
MM
1045
1046 if ((mysqlres = mmsql_query("SELECT * FROM nofrom", 20)) != NULL) {
1047 if ((mysql_num_rows(mysqlres)) > 0) {
42dd3825
MM
1048 int cnt = 0;
1049 MYSQL_ROW *row;
1050 unsigned long *lengths;
1051
47071c2b 1052 while ((row = (MYSQL_ROW *)mysql_fetch_row(mysqlres)) != NULL) {
42dd3825
MM
1053 lengths = mysql_fetch_lengths(mysqlres);
1054 if (row[0]) {
1055 char pat[64];
1056
1057 mm_memcpy(pat, row[0], lengths[0]);
1058 pat[lengths[0]] = 0;
46cf2cd5 1059 if ((alias_match(addr, pat)) != -1) {
42dd3825
MM
1060 res = TRUE;
1061 break;
47071c2b
MM
1062 }
1063 }
1064 cnt++;
1065 if (cnt > 64) {
1066 cnt = 0;
1067 pth_yield(NULL);
1068 }
1069 }
1070 }
1071 mysqlres = mmsql_free_result(mysqlres);
1072 }
1073
1074 return (res);
1075}
1076
1077
46cf2cd5
MM
1078/* Returns -1 if <str> does not match <pat> '*' and '?' wildcards pattern.
1079 * Otherwise returns > -1, a value representing the number of literal
1080 * characters in <pat> which exactly matched <str>. This us useful to evaluate
1081 * the best match among a list of patterns.
42dd3825 1082 */
46cf2cd5
MM
1083static int
1084alias_match(const char *str, const char *pat)
1085{
1086 int lit = 0;
1087
1088 for (; *pat != '*'; pat++, str++) {
1089 if (!(*str)) {
1090 if (*pat) return (-1);
1091 else return (lit);
47071c2b 1092 }
46cf2cd5
MM
1093 if (*str == *pat) lit++;
1094 else if(*pat != '?') return (-1);
47071c2b 1095 }
47071c2b 1096 while (pat[1] == '*') pat++;
47071c2b 1097 do {
46cf2cd5 1098 register int tmp;
47071c2b 1099
46cf2cd5
MM
1100 if ((tmp = alias_match(str, pat + 1)) != -1) return (lit + tmp);
1101 } while (*str++);
1102
1103 return (-1);
47071c2b
MM
1104}
1105
1106
1107/* Returns FALSE if this address doesn't exist in our local mailboxes.
1108 * Otherwise it returns information about the mailbox via supplied pointers.
1109 */
1110static bool
1111local_address(const char *address, long *maxsize, long *size, long *maxmsgs,
1112 long *msgs)
1113{
1114 bool res = FALSE;
1115 char line[1024];
1116 MYSQL_RES *mysqlres;
1117 MYSQL_ROW *row;
1118 unsigned int fields;
1119 unsigned long *lengths;
1120
1121 /* Query mysql to see if this address exists, and get limits/status */
1122 snprintf(line, 1000,
1123 "SELECT box_max_size,box_size,box_max_msgs,box_msgs FROM box\
1124 WHERE box_address='%s'", address);
1125
1126 if ((mysqlres = mmsql_query(line, mm_strlen(line))) != NULL) {
1127
1128 if ((mysql_num_rows(mysqlres)) == 1
1129 && (row = (MYSQL_ROW *)mysql_fetch_row(mysqlres)) != NULL) {
1130 if ((fields = mysql_num_fields(mysqlres)) == 4) {
1131 lengths = mysql_fetch_lengths(mysqlres);
1132 if (row[0] && row[1] && row[2] && row[3]) {
1133 mm_memcpy(line, row[0], lengths[0]);
1134 line[lengths[0]] = 0;
1135 *maxsize = atol(line);
1136 mm_memcpy(line, row[1], lengths[1]);
1137 line[lengths[1]] = 0;
1138 *size = atol(line);
1139 mm_memcpy(line, row[2], lengths[2]);
1140 line[lengths[2]] = 0;
1141 *maxmsgs = atol(line);
1142 mm_memcpy(line, row[3], lengths[3]);
1143 line[lengths[3]] = 0;
1144 *msgs = atol(line);
1145 res = TRUE;
1146 } else
1147 syslog(LOG_NOTICE, "* local_address() - row[x]");
1148 } else
1149 syslog(LOG_NOTICE,
1150 "* local_address() - mysql_num_fields()");
1151 }
1152
1153 mysqlres = mmsql_free_result(mysqlres);
1154 } else
1155 syslog(LOG_NOTICE, "* local_address() - mmsql_query()");
1156
1157 return (res);
1158}
1159
1160
1161/* Fills str which should be at least 32 bytes in lenght with current time */
1162static void
1163rfc_time(char *str)
1164{
1165 /* Thu, 07 Dec 2000 07:36:15 -0000 */
1166 char *days[] = {
1167 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
1168 };
1169 char *months[] = {
1170 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
1171 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
1172 };
1173 time_t secs;
1174 struct tm *gtim;
1175
1176 /* Calculate expiration time of the cookie */
1177 secs = time(NULL);
1178 gtim = gmtime(&secs);
1179
1180 snprintf(str, 32, "%s, %02d %s %04d %02d:%02d:%02d -0000",
1181 days[gtim->tm_wday], gtim->tm_mday, months[gtim->tm_mon],
1182 gtim->tm_year + 1900, gtim->tm_hour, gtim->tm_min,
1183 gtim->tm_sec);
1184}
1185
1186
1187/* Returns whether or not supplied address is valid, and if it is return the
1188 * parsed address in the supplied string. String should be at least 64 bytes.
1189 */
1190static bool
46cf2cd5 1191valid_address(clientenv *clenv, char *to, char *addr, int res)
47071c2b
MM
1192{
1193 char *ptr, *ptr2;
1194
1195 mm_strlower(addr);
1196
1197 /* First locate required @ */
1198 ptr = addr;
46cf2cd5
MM
1199 while (*ptr && *ptr != '@') ptr++;
1200 if (!*ptr) return (FALSE);
47071c2b
MM
1201
1202 /* Then scan to the left */
1203 ptr2 = ptr;
1204 ptr2--;
46cf2cd5
MM
1205 while (ptr2 >= addr && valid_char(*ptr2)) ptr2--;
1206 if (ptr2 == (ptr - 1)) return (FALSE);
47071c2b
MM
1207 ptr2++;
1208
1209 /* Now validate hostname part */
1210 ptr++;
42dd3825 1211 if (valid_host(clenv, ptr, res, FALSE)) {
47071c2b
MM
1212 mm_strncpy(to, ptr2, 63);
1213 return (TRUE);
1214 }
1215
1216 return (FALSE);
1217}
1218
1219
1220static bool
46cf2cd5 1221valid_host(clientenv *clenv, char *host, int res, bool addr)
47071c2b 1222{
ec182166 1223 register char *ptr;
47071c2b 1224
46cf2cd5 1225 if (addr && res != HOST_RES_MX && valid_ipaddress(host)) return (TRUE);
47071c2b 1226
ec182166 1227 mm_strlower(host);
47071c2b 1228 /* First make sure all characters are valid */
ec182166
MM
1229 for (ptr = host; *ptr; ptr++)
1230 if (!valid_char(*ptr)) return (FALSE);
47071c2b
MM
1231
1232 /* Now verify that all parts of the hostname are starting with
1233 * an alphanumeric char
1234 */
1235 ptr = host;
1236 while (*ptr) {
ec182166
MM
1237 if (!isalnum(*ptr)) return (FALSE);
1238 /* Find next host part */
1239 while (*ptr && *ptr != '.') ptr++;
1240 if (*ptr == '.') {
1241 ptr++;
1242 continue;
47071c2b 1243 }
ec182166 1244 if (!*ptr) break;
47071c2b
MM
1245 ptr++;
1246 }
1247
ec182166 1248 /* Hostname seems valid */
46cf2cd5 1249 if (res == HOST_RES_MX) {
ec182166
MM
1250 char answer[64];
1251
1252 /* Check for an MX DNS IP address entry for it */
1253 if ((a_res_query(clenv, host, C_IN, T_MX, answer, sizeof(answer) - 1))
1254 == -1)
1255 return (FALSE);
46cf2cd5
MM
1256 } else if (res == HOST_RES) {
1257 char answer[64];
1258
1259 /* Check if hostname resolves to normal A record */
1260 if ((a_res_query(clenv, host, C_IN, T_A, answer, sizeof(answer) - 1))
1261 == -1)
1262 return (FALSE);
47071c2b
MM
1263 }
1264
ec182166 1265 return (TRUE);
47071c2b
MM
1266}
1267
1268
1269/* A useful function which returns wether or not a character is valid
1270 * in an email address or hostname
1271 */
1272static bool
1273valid_char(char c)
1274{
1275 char allow[] = "abcdefghijklmnopqrstuvwxyz1234567890.-_", *ptr = allow;
1276
1277 while (*ptr) {
1278 if (*ptr == c)
1279 return (TRUE);
1280 ptr++;
1281 }
1282
1283 return (FALSE);
1284}
1285
1286
46cf2cd5 1287/* Some more parsing magic for IP address sanity checking */
ec182166
MM
1288static bool
1289valid_ipaddress(const char *addr)
1290{
46cf2cd5 1291 char unit[5], *uptr, *utptr;
ec182166
MM
1292 int units;
1293
46cf2cd5
MM
1294 for (units = 0, uptr = unit, utptr = unit + 4; uptr < utptr; addr++) {
1295 if (!*addr || *addr == '.') {
ec182166
MM
1296 if (uptr > unit && units < 4) {
1297 register int n;
1298
1299 *uptr = '\0';
1300 n = atoi(unit);
1301 if (n < 0 || n > 255) break;
1302 uptr = unit;
1303 units++;
1304 } else return (FALSE);
46cf2cd5
MM
1305 if (!*addr) break;
1306 } else if (isdigit(*addr)) *uptr++ = *addr;
1307 else return (FALSE);
1308 }
ec182166
MM
1309 if (!(units == 4 && *addr == '\0')) return (FALSE);
1310
1311 return (TRUE);
1312}
1313
1314
47071c2b
MM
1315static int
1316validate_msg_line(char *line, ssize_t *len, int *res, void *udata)
1317{
1318 register struct validate_udata *ud = udata;
1319
1320 /* Count hops */
1321 if (ud->hops != -1) {
1322 if (!(mm_strncmp(line, "Received:", 9))) {
1323 ud->hops++;
1324 if (ud->hops > CONF.MAX_HOPS) {
1325 /* Exceeded maximum allowed number of "Received:" lines */
1326 *res = CFDBRB_HOPS;
1327 return (FDBRB_STOP);
1328 } else ud->nhops = 0;
1329 } else {
1330 ud->nhops++;
1331 if (ud->nhops > 5)
1332 ud->hops = -1;
1333 }
1334 }
1335
1336 /* Process .* lines */
1337 if (*len) {
1338 if (*line == '.') {
1339 /* Only '.' on line, stop reading */
1340 if (*len == 1) return (FDBRB_STOP);
1341 /* Strip starting . from line */
1342 mm_memmov(line, line + 1, *len);
1343 (*len)--;
1344 }
1345 }
1346
1347 return (FDBRB_OK);
1348}
1349
1350
1351/* This function is called by STATE_DATA and permits the client to send
1352 * the message data, respecting expected limits. Returns FALSE if the state
1353 * should switch to STATE_ERROR, on fatal error (eg: out of memory)
1354 */
1355static bool
1356do_data(clientenv *clenv)
1357{
1358 char line[512], line2[2048], smtptime[32], *tmp, *query;
1359 struct fdbrb_buffer *fdbrb;
1360 int res, err = DATA_INTERNAL;
1361 bool ok = FALSE;
1362 rcptnode *rnode;
1363 struct validate_udata ud;
1364
1365 reply(clenv->fdb, data_msg[DATA_SUBMIT].code, FALSE,
1366 data_msg[DATA_SUBMIT].msg);
1367 fdbflushw(clenv->fdb);
1368
1369 /* Call our famous fdbreadbuf() which will read lines in a single buffer
1370 * and validate them via the validate_msg_line() function (above).
1371 * We restict the maximum length of a single line to 1024 characters
1372 * and are starting with an initial buffer of 32K, buffer which will
1373 * double in size whenever required. Of course don't read more than
1374 * CONF.MAX_DATA_SIZE bytes or CONF.MAX_DATA_LINES lines.
1375 * See mmfd(3) man page for details, and mmlib/mmfd.c
1376 */
1377 ud.hops = ud.nhops = 0;
1378 res = fdbreadbuf(&fdbrb, clenv->fdb, 32768, 1024, CONF.MAX_DATA_SIZE,
1379 CONF.MAX_DATA_LINES, validate_msg_line, &ud, FALSE);
1380 /* Map results to DATA suitable ones */
1381 switch (res) {
1382 case FDBRB_MEM:
1383 mmsyslog(0, LOGLEVEL, "%08X * Out of memory", clenv->id);
1384 err = DATA_INTERNAL;
e334174e 1385 REGISTER_ERROR(clenv);
47071c2b
MM
1386 break;
1387 case FDBRB_OVERFLOW:
1388 mmsyslog(0, LOGLEVEL, "%08X * Message size too large", clenv->id);
1389 err = DATA_OVERFLOW;
e334174e 1390 REGISTER_ERROR(clenv);
47071c2b
MM
1391 break;
1392 case FDBRB_TIMEOUT:
1393 mmsyslog(0, LOGLEVEL, "%08X * Input timeout", clenv->id);
1394 if (CONF.STATFAIL_TIMEOUT)
1395 mmstat(&clenv->pstat, STAT_UPDATE, 1, "mmsmtpd.failed.timeout.%s",
1396 clenv->c_ipaddr);
1397 break;
1398 case FDBRB_EOF:
1399 mmsyslog(0, LOGLEVEL, "%08X * Unexpected EOF", clenv->id);
1400 break;
1401 case CFDBRB_HOPS:
1402 mmsyslog(0, LOGLEVEL, "%08X * Too many hops", clenv->id);
1403 err = DATA_HOPS;
e334174e 1404 REGISTER_ERROR(clenv);
47071c2b
MM
1405 break;
1406 case FDBRB_OK:
1407 ok = TRUE;
1408 break;
1409 }
1410
1411 if (ok) {
1412 /* Allocate query buffer for mysql_real_query(), should be large
1413 * enough to handle the worst of cases where each character would
1414 * be escaped to two chars, and must also hold the rest of the
1415 * query string. We first process the message data through
1416 * mysql_escape_string(), leaving enough room for the query and our
1417 * "Received:" line, which will be copied before the message buffer
1418 * for each RCPT. The message buffer will start at offset 2048
1419 * to make sure that there is enough room to insert the
1420 * RCPT-specific data (query+received).
1421 */
1422 if ((query = malloc((fdbrb->current * 2) + 2053))) {
1423 size_t len, qlen, tlen, clen;
1424
1425 /* Prepare message buffer for mysql query */
1426 clen = fdbrb->current; /* Used after freeing buffer as well */
1427 tmp = &query[2048];
1428 tmp += mysql_escape_string(tmp, fdbrb->array, clen);
1429 *tmp++ = '\'';
1430 *tmp++ = ')';
1431 *tmp++ = '\0';
1432 qlen = tmp - &query[2048];
1433 rfc_time(smtptime);
1434 fdbfreebuf(&fdbrb); /* Free immediately */
1435
1436 /* For each RCPT, create query and execute it */
1437 rnode = (rcptnode *)clenv->rcpt.top;
1438 while (rnode) {
1439
1440 /* Use the common message buffer, but append the query and
1441 * message line before it (in it's 2048 bytes free area)
1442 */
1443 snprintf(line, 511, "Received: from %s ([%s] HELO=%s)\r\n\t\
1444by %s (%s) with SMTP\r\n\tid %08lX-%lu for <%s>;\r\n\t%s\r\n",
1445 clenv->c_hostname ? clenv->c_hostname : "(unresolved)",
1446 clenv->c_ipaddr,
1447 clenv->helo ? clenv->helo : "(unidentified)",
1448 clenv->iface->hostname, DAEMON_VERSION, clenv->id,
1449 clenv->messages, rnode->foraddress, smtptime);
1450 tlen = mm_strlen(line) + clen;
1451 snprintf(line2, 511, "INSERT INTO mail (mail_box,\
5eb34fba
MM
1452mail_created,mail_size,mail_data) VALUES('%s',NOW(),%ld,'", rnode->address,
1453 (long)tlen);
47071c2b
MM
1454 tmp = line2 + mm_strlen(line2);
1455 tmp += mysql_escape_string(tmp, line, mm_strlen(line));
1456 len = tmp - line2;
1457 tmp = &query[2048 - len];
1458 mm_memcpy(tmp, line2, len);
1459
1460 /* Query buffer prepared, execute query. This glock is
1461 * required for safety between the two queries which have
1462 * to be performed within a single transaction. See
1463 * mmlib/mmsql.c for implementation details; Currently uses
1464 * MySQL GET_LOCK() and RELEASE_LOCK().
1465 */
1466 mmsql_glock("mmmail_boxmail");
1467 if (!mmsql_command(tmp, qlen + len)) {
1468 mmsyslog(0, LOGLEVEL,
1469 "%08X * Error adding message into mailbox",
1470 clenv->id);
1471 ok = FALSE;
1472 break;
1473 } else {
5eb34fba
MM
1474 snprintf(line, 1000, "UPDATE box SET box_size=box_size+\
1475%ld,box_msgs=box_msgs+1,box_in=NOW() WHERE box_address='%s'",
1476 (long)tlen, rnode->address);
47071c2b
MM
1477 if (!mmsql_command(line, mm_strlen(line))) {
1478 mmsyslog(0, LOGLEVEL,
1479 "%08X * Error updating mailbox counters",
1480 clenv->id);
1481 ok = FALSE;
1482 }
1483 }
1484 mmsql_gunlock("mmmail_boxmail");
1485 if (!ok) break;
1486
1487 rnode = (rcptnode *)rnode->nod.next;
1488 }
1489
1490 free(query);
1491 } else {
1492 mmsyslog(0, LOGLEVEL, "%08X * Out of memory", clenv->id);
e334174e 1493 REGISTER_ERROR(clenv);
47071c2b
MM
1494 ok = FALSE;
1495 }
1496 }
1497
1498 fdbfreebuf(&fdbrb); /* Internally only frees if not already freed */
1499 if (ok) err = DATA_OK;
1500 reply(clenv->fdb, data_msg[err].code, FALSE, data_msg[err].msg);
1501
1502 /* Reset mail state (and free RCPTs) */
1503 init_clientenv(clenv, FALSE);
1504
1505 return (ok);
1506}
1507
1508
1509
1510
1511/* This is the main function that is called to serve a client.
1512 * It comports the main loop and state switcher.
1513 */
1514static int
1515handleclient(unsigned long id, int fd, clientlistnode *clientlnode,
1516 struct iface *iface, struct async_clenv *aclenv)
1517{
1518 char buffer[1024], ipaddr[20], *tmp;
1519 int len, state, nstate, timeout, dstatus;
1520 clientenv *clenv;
1521 struct sockaddr_in *sinaddr;
1522 fdbuf *fdb;
1523 int64_t data_in, data_out;
1524 unsigned long rcpt_in, messages_in, time_total;
1525 time_t time_start, time_end;
47071c2b
MM
1526
1527 data_in = data_out = rcpt_in = messages_in = 0;
1528 dstatus = MMS_RESOURCE_ERROR;
1529 timeout = clientlnode->timeout;
1530 clenv = NULL;
1531
1532 /* Obtain IP address of client */
1533 sinaddr = (struct sockaddr_in *)&clientlnode->client;
1534 if ((tmp = inet_ntoa(sinaddr->sin_addr))) mm_strncpy(ipaddr, tmp, 19);
1535 else mm_strncpy(ipaddr, "0.0.0.0", 8);
1536
1537 if (clientlnode->hostname)
1538 /* Log user's address and hostname */
1539 mmsyslog(1, LOGLEVEL, "%08X Connect: [%s] - (%s)", id, ipaddr,
1540 clientlnode->hostname);
1541 else
1542 /* Log user's address only */
1543 mmsyslog(1, LOGLEVEL, "%08X Connect: [%s]", id, ipaddr);
1544
1545 time_start = time(NULL);
1546
5eb34fba 1547 if ((fdb = fdbopen(&gfdf, &fdbc, fd, 8192, 8192, CONF.BANDWIDTH_IN * 1024,
47071c2b
MM
1548 CONF.BANDWIDTH_OUT * 1024, timeout, timeout))) {
1549
1550 /* Allocate our clientenv to share with state functions */
1551 if ((clenv = alloc_clientenv())) {
1552
1553 /* Set some configuration options such as max_rcpts,
1554 * max_mesg_lines, max_mesg_size, hostname...
1555 */
1556 clenv->fdb = fdb;
1557 clenv->buffer = buffer;
1558 clenv->errors = 0;
1559 clenv->timeout = timeout;
1560 clenv->c_hostname = clientlnode->hostname;
1561 clenv->c_ipaddr = ipaddr;
1562 clenv->id = id;
1563 clenv->iface = iface;
1564 clenv->aclenv = aclenv;
1565
1566 reply(fdb, 220, FALSE, "%s (%s (%s)) Service ready",
1567 iface->hostname, DAEMON_NAME, DAEMON_VERSION);
1568 state = STATE_ALL;
1569 dstatus = MMS_NORMAL;
1570
1571 mmstat(&clenv->pstat, STAT_UPDATE, 1, "mmsmtpd.total.connections");
1572
1573 mmstat_transact(&clenv->vstat, TRUE);
1574 mmstat(&clenv->vstat, STAT_UPDATE, 1,
1575 "mmsmtpd.current.connections");
1576 mmstat(&clenv->vstat, STAT_UPDATE, 1, "mmsmtpd.who.%s",
1577 clenv->c_ipaddr);
1578 mmstat_transact(&clenv->vstat, FALSE);
1579
1580 /* Main state switcher loop */
1581 while (TRUE) {
5eb34fba 1582 register int i = 0;
47071c2b
MM
1583 register int32_t chash;
1584 register bool valid;
1585
1586 fdbflushw(fdb);
1587 if ((len = fdbgets(fdb, buffer, 1000, FALSE)) > -1) {
1588
1589 /* If there were too many errors, exit accordingly */
1590 if (clenv->errors > CONF.MAX_ERRORS) {
1591 reply(fdb, 421, FALSE, "Too many errors");
1592 dstatus = MMS_MANY_ERRORS;
1593 break;
1594 }
1595 /* Verify if command matches an existing one */
1596 valid = FALSE;
1597 if ((chash = mm_strpack32(buffer, 4)) != -1) {
1598 for (i = 0; commands[i].name; i++) {
1599 if (commands[i].hash == chash) {
1600 valid = TRUE;
1601 break;
1602 }
1603 }
1604 }
1605
1606 if (valid) {
1607 register int (*func)(clientenv *);
1608
1609 mmsyslog(commands[i].loglevel, LOGLEVEL,
1610 "%08X %s", id, buffer);
1611
1612 if ((func = states[state].functions[i])) {
1613
1614 /* Valid command, process it in current state */
1615 nstate = func(clenv);
1616 if (nstate == STATE_END || nstate == STATE_ERROR)
1617 break;
1618 if (nstate != STATE_CURRENT)
1619 state = nstate;
1620
1621 } else {
1622 /* Unimplemented command for this state */
e334174e 1623 REGISTER_ERROR(clenv);
47071c2b
MM
1624 if (!reply(fdb, states[state].errcode, FALSE,
1625 states[state].errtext))
1626 break;
1627 }
1628
1629 } else {
1630 mmsyslog(3, LOGLEVEL, "%08X %s", id, buffer);
1631 reply(fdb, 500, FALSE,
1632 "Syntax error or unknown command, type HELP");
e334174e 1633 REGISTER_ERROR(clenv);
47071c2b
MM
1634 }
1635
1636 } else {
1637 /* Input error */
1638 if (len == FDB_TIMEOUT) {
1639 dstatus = MMS_INPUT_TIMEOUT;
1640 break;
1641 } else if (len == FDB_ERROR) {
1642 dstatus = MMS_INPUT_ERROR;
1643 break;
1644 } else {
1645 dstatus = MMS_UNKNOWN;
1646 break;
1647 }
1648 }
1649
1650 }
1651
1652 messages_in = clenv->messages;
1653 rcpt_in = clenv->rcpts;
5eb34fba
MM
1654 data_in = FDBBYTESR(fdb);
1655 data_out = FDBBYTESW(fdb);
47071c2b
MM
1656
1657 mmstat_transact(&clenv->vstat, TRUE);
1658 mmstat(&clenv->vstat, STAT_UPDATE, -1,
1659 "mmsmtpd.who.%s", clenv->c_ipaddr);
1660 mmstat(&clenv->vstat, STAT_UPDATE, -1,
1661 "mmsmtpd.current.connections");
1662 mmstat_transact(&clenv->vstat, FALSE);
1663
1664 mmstat_transact(&clenv->pstat, TRUE);
1665 mmstat(&clenv->pstat, STAT_UPDATE, messages_in,
1666 "mmsmtpd.total.messages-in");
1667 mmstat(&clenv->pstat, STAT_UPDATE, data_in,
1668 "mmsmtpd.total.in");
1669 mmstat(&clenv->pstat, STAT_UPDATE, data_out,
1670 "mmsmtpd.total.out");
1671 mmstat_transact(&clenv->pstat, FALSE);
1672
1673 /* Free our state-shared clenv */
1674 clenv = free_clientenv(clenv);
1675 } else
1676 mmsyslog(0, LOGLEVEL,
1677 "* handleclient() - Could not allocate state-shared clenv");
1678
1679 fdbclose(fdb);
1680 } else
1681 mmsyslog(0, LOGLEVEL, "* handleclient() - fdbopen()");
1682
1683 /* Log results */
1684 time_end = time(NULL);
1685 time_total = time_end - time_start;
1686 mmsyslog(1, LOGLEVEL,
1687 "%08X Closed: [%s] - (Seconds: %lu, Data in: %lld out: %lld, \
1688RCPTs: %lu, Messages in: %lu, Status: %s)", id, ipaddr, time_total, data_in,
5eb34fba 1689 data_out, rcpt_in, messages_in, MMS_RSTRING(dstatus));
47071c2b
MM
1690
1691 return (0);
1692}
1693
1694
5eb34fba
MM
1695/* mmfd library thread support functions */
1696
1697
1698static void *
1699_pth_mutex_create(void)
1700{
1701 struct mutexnode *mnod;
1702
1703 pth_mutex_acquire(&mutexes_lock, FALSE, NULL);
1704 mnod = (struct mutexnode *)allocnode(mutexes, FALSE);
1705 pth_mutex_release(&mutexes_lock);
1706
1707 if (mnod)
1708 pth_mutex_init(&mnod->mutex);
1709
1710 return ((void *)mnod);
1711}
1712
1713
1714static void *
1715_pth_mutex_destroy(void *mtx)
1716{
1717 /* struct mutexnode *mnod = mtx; */
1718
1719 /* pth_mutex_destroy(&mnod->mutex); */
1720 pth_mutex_acquire(&mutexes_lock, FALSE, NULL);
1721 freenode(mtx);
1722 pth_mutex_release(&mutexes_lock);
1723
1724 return (NULL);
1725}
1726
1727
1728static void
1729_pth_mutex_lock(void *mtx)
1730{
1731 struct mutexnode *mnod = mtx;
1732
1733 pth_mutex_acquire(&mnod->mutex, FALSE, NULL);
1734}
1735
1736
1737static void
1738_pth_mutex_unlock(void *mtx)
1739{
1740 struct mutexnode *mnod = mtx;
1741
1742 pth_mutex_release(&mnod->mutex);
1743}
1744
1745
1746static void
1747_pth_thread_yield(void)
1748{
1749 pth_yield(NULL);
1750}
1751
1752
47071c2b
MM
1753/* Here are our real asynchroneous functions, called by the slave processes */
1754
1755
1756static void
1757async_resquery(struct async_msg *msg)
1758{
1759 struct async_resquery_msg *amsg = (void *)msg;
1760
1761 amsg->un.res.res = res_query(amsg->un.args.host, amsg->un.args.r_class,
1762 amsg->un.args.r_type, amsg->un.res.answer, 127);
1763}
1764
1765
1766/* And our wrapper functions calling the asynchroneous device */
1767
1768
1769static int
1770a_res_query(clientenv *clenv, const char *dname, int class, int type,
1771 u_char *answer, int anslen)
1772{
1773 struct async_resquery_msg *amsg = (void *)clenv->aclenv->msg;
1774 int res;
1775
1776 mm_strncpy(amsg->un.args.host, dname, 127);
1777 amsg->un.args.r_class = class;
1778 amsg->un.args.r_type = type;
1779 async_call(clenv->aclenv, ASYNC_RESQUERY);
1780 if ((res = amsg->un.res.res) != -1)
1781 mm_strncpy(answer, amsg->un.res.answer, anslen);
1782
1783 return (res);
1784}