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