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