1 /* This file is part of Mailfromd.
2    Copyright (C) 2005-2021 Sergey Poznyakoff
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 3, or (at your option)
7    any later version.
8 
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16 
17 #ifdef HAVE_CONFIG_H
18 # include <config.h>
19 #endif
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <unistd.h>
23 #include <stdlib.h>
24 #include <signal.h>
25 #include <stdio.h>
26 #include <sys/socket.h>
27 #include <netinet/in.h>
28 #include <arpa/inet.h>
29 #include <netdb.h>
30 
31 #include <mailutils/mailutils.h>
32 #include <mailutils/cli.h>
33 #include <mailutils/daemon.h>
34 
35 #include "libmf.h"
36 #include "srvcfg.h"
37 #include "callout.h"
38 #include "mfdb.h"
39 #include "callout-dbgmod.h"
40 
41 char *ehlo_domain;
42 char *mailfrom_address;
43 int enable_vrfy;
44 char *session_id;
45 
46 /* FIXME: Rewrite I/O via mu_stream */
47 
48 struct callout_command {
49 	const char *command;
50 	int argmin;
51 	int argmax;
52 	int (*handler) (FILE *, int, char **);
53 };
54 
55 enum callout_mode {
56 	cmode_mx_first,
57 	cmode_mx_only,
58 	cmode_host_only,
59 	cmode_host_first
60 };
61 
62 struct vrfy_queue {
63 	struct vrfy_queue *next;
64 	unsigned serial;
65 	enum callout_mode mode;
66 	char *hostname;
67 	smtp_io_t io;
68 	mf_status result;
69 	FILE *file;
70 };
71 
72 static struct vrfy_queue *head, *tail;
73 static unsigned serial;
74 
75 int
trimcrlf(char * buf)76 trimcrlf(char *buf)
77 {
78 	size_t len = strlen(buf);
79 	if (len >= 1 && buf[len-1] == '\n') {
80 		buf[--len] = 0;
81 		if (len >= 1 && buf[len-1] == '\r')
82 			buf[--len] = 0;
83 		return 0;
84 	}
85 	return 1;
86 }
87 
88 #define SPFX "S: "
89 #define SPFXSIZ (sizeof(SPFX)-1)
90 #define SBUFSIZ 512
91 
92 static void
writeout(FILE * fp,const char * fmt,...)93 writeout(FILE *fp, const char *fmt, ...)
94 {
95 	va_list ap;
96 
97 	va_start(ap, fmt);
98 	if (mu_debug_level_p(MF_SOURCE_SAVSRV, MU_DEBUG_PROT)) {
99 		static char fmtbuf[SPFXSIZ + SBUFSIZ + 3 + 1 + 1] = SPFX;
100 		size_t fmtlen = strlen(fmt);
101 		char *dots = NULL;
102 		int more = fmt[fmtlen - 1] != '\n';
103 		if (fmtlen > SBUFSIZ) {
104 			fmtlen = SBUFSIZ;
105 			dots = "...";
106 		}
107 		if (fmt[fmtlen - 1] == '%' && fmt[fmtlen - 2] != '%')
108 			fmtlen--;
109 		memcpy(fmtbuf + SPFXSIZ, fmt, fmtlen);
110 		fmtbuf[SPFXSIZ + fmtlen] = 0;
111 		trimcrlf(fmtbuf);
112 		if (dots)
113 			strcat(fmtbuf, dots);
114 		if (more)
115 			strcat(fmtbuf, "\\");
116 		mu_diag_voutput(MU_LOG_DEBUG, fmtbuf, ap);
117 	}
118 	vfprintf(fp, fmt, ap);
119 	va_end(ap);
120 }
121 
122 static void
defproctitle()123 defproctitle()
124 {
125 	if (session_id)
126 		mf_proctitle_format("callout server: %s", session_id);
127 	else
128 		mf_proctitle_format("callout server");
129 }
130 
131 static void
vrfy_free(struct vrfy_queue * qp)132 vrfy_free(struct vrfy_queue *qp)
133 {
134 	smtp_io_free(qp->io);
135 	free(qp->hostname);
136 	free(qp);
137 }
138 
139 static int
vrfy_del(unsigned num)140 vrfy_del(unsigned num)
141 {
142 	struct vrfy_queue *qp, *prev;
143 
144 	if (!head)
145 		return -1;
146 	if (head->serial == num) {
147 		qp = head;
148 		head = head->next;
149 		if (!head)
150 			tail = NULL;
151 		vrfy_free(qp);
152 		return 0;
153 	}
154 
155 	for (prev = head, qp = head->next; qp; prev = qp, qp = qp->next) {
156 		if (qp->serial == num) {
157 			prev->next = qp->next;
158 			vrfy_free(qp);
159 			return 0;
160 		}
161 	}
162 	return -1;
163 }
164 
165 static void
verify(struct vrfy_queue * qp,int hard)166 verify(struct vrfy_queue *qp, int hard)
167 {
168 	mf_status rc;
169 #define STATUS_FIXUP(code)						\
170 	do {								\
171 		if (hard && (code) == mf_timeout)			\
172 			(code) = mf_not_found;				\
173         } while(0)
174 
175 	smtp_io_set_timeouts(qp->io,
176 			     hard ? smtp_timeout_hard : smtp_timeout_soft);
177 	switch (qp->mode) {
178 	case cmode_mx_first:
179 		mf_proctitle_format("callout(mx): %s: %010u:<%s>",
180 				    smtp_io_id(qp->io),
181 				    qp->serial,
182 				    smtp_io_email(qp->io));
183 		if (!hard) {
184 			rc = cache_get(smtp_io_email(qp->io));
185 			if (rc != mf_failure)
186 				break;
187 		}
188 		rc = callout_standard(qp->io);
189 		STATUS_FIXUP(rc);
190 		cache_insert(smtp_io_email(qp->io), rc);
191 		break;
192 
193 	case cmode_mx_only:
194 		mf_proctitle_format("callout(mx only): %s: %010u:<%s>@%s",
195 				    smtp_io_id(qp->io),
196 				    qp->serial,
197 				    smtp_io_email(qp->io),
198 				    qp->hostname);
199 		rc = callout_mx(qp->io, qp->hostname, NULL);
200 		STATUS_FIXUP(rc);
201 		break;
202 
203 	case cmode_host_first:
204 		mf_proctitle_format("callout(host first): %s: %010u:<%s>@%s",
205 				    smtp_io_id(qp->io),
206 				    qp->serial,
207 				    smtp_io_email(qp->io),
208 				    qp->hostname);
209 		if (!hard) {
210 			rc = cache_get2(smtp_io_email(qp->io), qp->hostname);
211 			if (rc != mf_failure)
212 				break;
213 		}
214 		rc = callout_strict(qp->io, qp->hostname);
215 		STATUS_FIXUP(rc);
216 		cache_insert2(smtp_io_email(qp->io),
217 			      qp->hostname, rc);
218 		break;
219 
220 	case cmode_host_only:
221 		mf_proctitle_format("callout(host only): %s: %010u:<%s>@%s",
222 				    smtp_io_id(qp->io),
223 				    qp->serial,
224 				    smtp_io_email(qp->io),
225 				    qp->hostname);
226 		rc = callout_host(qp->io, qp->hostname);
227 		STATUS_FIXUP(rc);
228 	}
229 	qp->result = rc;
230 }
231 
232 static void
run_queue(FILE * fp,int hard)233 run_queue(FILE *fp, int hard)
234 {
235 	struct vrfy_queue *qp;
236 
237 	for (qp = head; qp; qp = qp->next) {
238 		qp->file = fp;
239 		verify(qp, hard);
240 	}
241 	defproctitle();
242 }
243 
244 static void
savsrv_smtp_io_callback(void * data,const char * key,const char * value)245 savsrv_smtp_io_callback(void *data, const char *key, const char *value)
246 {
247 	struct vrfy_queue *qp = data;
248 
249 	if (qp->file)
250 		writeout(qp->file, "* %010u %s %s\r\n",
251 			 qp->serial, key, value);
252 }
253 
254 static char *
getval(char * input)255 getval(char *input)
256 {
257 	char *p = strchr(input, '=');
258 	if (!p)
259 		return NULL;
260 	*p++ = 0;
261 	return p;
262 }
263 
264 static struct vrfy_queue *
addq(int argc,char ** argv,const char ** errp)265 addq(int argc, char **argv, const char **errp)
266 {
267 	int i;
268 	struct vrfy_queue *qp = mu_alloc(sizeof(*qp));
269 	char *email;
270 	char *host = NULL;
271 	char *ehlo = ehlo_domain;
272 	char *mailfrom = mailfrom_address;
273 	enum callout_mode mode = cmode_mx_first;
274 
275 	email = argv[1];
276 	for (i = 2; i < argc; i++) {
277 		char *attr = argv[i];
278 		char *val = getval(attr);
279 
280 		if (!val) {
281 			*errp = "syntax error";
282 			return NULL;
283 		}
284 		if (!strcasecmp(attr, "host")) {
285 			host = val;
286 		} else if (!strcasecmp(attr, "mode")) {
287 			if (!strcmp(val, "mxfirst") || !strcmp(val, "default"))
288 				mode = cmode_mx_first;
289 			else if (!strcmp(val, "mxonly"))
290 				mode = cmode_mx_only;
291 			else if (!strcmp(val, "hostonly"))
292 				mode = cmode_host_only;
293 			else if (!strcmp(val, "hostfirst"))
294 				mode = cmode_host_first;
295 		} else if (!strcasecmp(attr, "ehlo")) {
296 			ehlo = val;
297 		} else if (!strcasecmp(attr, "mailfrom")) {
298 			mailfrom = val;
299 		} else {
300 			*errp = "unknow attribute";
301 			return NULL;
302 		}
303 	}
304 
305 	switch (mode) {
306 	case cmode_host_first:
307 	case cmode_host_only:
308 	case cmode_mx_only:
309 		if (!host) {
310 			*errp = "mode requires host";
311 			return NULL;
312 		}
313 		break;
314 	case cmode_mx_first:
315 		break;
316 	}
317 
318 	qp->serial = serial++;
319 	qp->result = mf_temp_failure; /* FIXME */
320 	qp->io = smtp_io_create(session_id ? session_id : email,
321 				smtp_timeout_hard,
322 				savsrv_smtp_io_callback,
323 				qp);
324 	smtp_io_setup_callout(qp->io, email, ehlo, mailfrom);
325 	switch (mode) {
326 	case cmode_host_first:
327 	case cmode_host_only:
328 	case cmode_mx_only:
329 		qp->hostname = mu_strdup(host);
330 		break;
331 	case cmode_mx_first:
332 		qp->hostname = NULL;
333 		break;
334 	}
335 	qp->mode = mode;
336 
337 	qp->next = NULL;
338 	if (tail)
339 		tail->next = qp;
340 	else
341 		head = qp;
342 	tail = qp;
343 	return qp;
344 }
345 
346 int
cmd_vrfy(FILE * fp,int argc,char ** argv)347 cmd_vrfy(FILE *fp, int argc, char **argv)
348 {
349 	const char *errp = "syntax error";
350 	struct vrfy_queue *qp = addq(argc, argv, &errp);
351 	if (qp)
352 		writeout(fp, "OK %010u\r\n", qp->serial);
353 	else
354 		writeout(fp, "NO %s\r\n", errp);
355 	return 0;
356 }
357 
358 int
cmd_get(FILE * fp,int argc,char ** argv)359 cmd_get(FILE *fp, int argc, char **argv)
360 {
361 	int i;
362 
363 	for (i = 1; i < argc; i++) {
364 		char *attr = argv[i];
365 		char *val;
366 		if (!strcasecmp(attr, "ehlo"))
367 			val = ehlo_domain;
368 		else if (!strcasecmp(attr, "mailfrom"))
369 			val = mailfrom_address;
370 		/* FIXME: Timeouts */
371 		else
372 			val = NULL;
373 		if (val)
374 			writeout(fp, "* %s=%s\r\n", attr, val);
375 	}
376 	writeout(fp, "OK\r\n");
377 	return 0;
378 }
379 
380 int
cmd_sid(FILE * fp,int argc,char ** argv)381 cmd_sid(FILE *fp, int argc, char **argv)
382 {
383 	if (session_id)
384 		free(session_id);
385 	session_id = mu_strdup(argv[1]);
386 	defproctitle();
387 	writeout(fp, "OK\r\n");
388 	return 0;
389 }
390 
391 int
cmd_timeout(FILE * fp,int argc,char ** argv)392 cmd_timeout(FILE *fp, int argc, char **argv)
393 {
394 	time_t to[SMTP_NUM_TIMEOUT];
395 	int i;
396 
397 	for (i = 1; i < argc; i++) {
398 		char *p;
399 		unsigned long n = strtoul(argv[i], &p, 10);
400 		if (*p) {
401 			writeout(fp, "NO syntax error in #%d\r\n", i);
402 			return 0;
403 		}
404 		to[i-1] = (time_t) n;
405 	}
406 	memcpy(smtp_timeout_soft, to, sizeof(to));
407 	writeout(fp, "OK timeouts set\r\n");
408 	return 0;
409 }
410 
411 int
cmd_run(FILE * fp,int argc,char ** argv)412 cmd_run(FILE *fp, int argc, char **argv)
413 {
414 	struct vrfy_queue *qp, *prev;
415 	run_queue(fp, 0);
416 	writeout(fp, "OK");
417 
418 	for (qp = head, prev = NULL; qp; ) {
419 		struct vrfy_queue *next = qp->next;
420 		mf_status result = (qp->result == mf_timeout) ?
421 			             mf_temp_failure : qp->result;
422 		writeout(fp, " %010u=%s", qp->serial,
423 			 mf_status_str(result));
424 		if (qp->result != mf_failure && qp->result != mf_timeout) {
425 			if (qp == tail)
426 				tail = prev;
427 			vrfy_free(qp);
428 			if (prev)
429 				prev->next = next;
430 			else
431 				head = next;
432 		} else
433 			prev = qp;
434 		qp = next;
435 	}
436 	writeout(fp, "\r\n");
437 	return 0;
438 }
439 
440 int
cmd_drop(FILE * fp,int argc,char ** argv)441 cmd_drop(FILE *fp, int argc, char **argv)
442 {
443 	if (strcasecmp(argv[1], "ALL") == 0) {
444 		struct vrfy_queue *qp;
445 
446 		for (qp = head; qp; ) {
447 			struct vrfy_queue *next = qp->next;
448 			vrfy_free(qp);
449 			qp = next;
450 		}
451 		head = tail = NULL;
452 	} else {
453 		char *p;
454 		unsigned num = strtoul(argv[1], &p, 10);
455 		if (*p)
456 			writeout(fp, "NO syntax error\r\n");
457 		else if (vrfy_del(num))
458 			writeout(fp, "NO entry not found\r\n");
459 		else
460 			writeout(fp, "OK\r\n");
461 	}
462 	return 0;
463 }
464 
465 int
cmd_quit(FILE * fp,int argc,char ** argv)466 cmd_quit(FILE *fp, int argc, char **argv)
467 {
468 	writeout(fp, "OK bye\r\n");
469 	return 1;
470 }
471 
472 static struct callout_command callout_command_tab[] = {
473 	{ "VRFY", 2, 0, cmd_vrfy },
474 	{ "GET", 2, 0, cmd_get },
475 	/* FIXME: SET */
476 	{ "SID", 2, 2, cmd_sid },
477 	{ "TIMEOUT", SMTP_NUM_TIMEOUT+1, SMTP_NUM_TIMEOUT+1, cmd_timeout },
478 	{ "RUN", 1, 1, cmd_run },
479 	{ "QUIT", 1, 1, cmd_quit },
480 	{ "DROP", 2, 2, cmd_drop },
481 	{ NULL }
482 };
483 
484 static struct callout_command *
find_command(const char * input)485 find_command(const char *input)
486 {
487 	struct callout_command *cmd;
488 
489 	for (cmd = callout_command_tab; cmd->command; cmd++) {
490 		if (strcasecmp(cmd->command, input) == 0)
491 			return cmd;
492 	}
493 	return NULL;
494 }
495 
496 
497 int
callout_session_server(const char * id,int fd,struct sockaddr const * sa,socklen_t len,void * server_data,void * srvman_data)498 callout_session_server(const char *id, int fd,
499 		       struct sockaddr const *sa, socklen_t len,
500 		       void *server_data, void *srvman_data)
501 {
502 	FILE *fp = fdopen(fd, "w+");
503 	char buf[1024];
504 	int longline = 0;
505 
506 	signal(SIGPIPE, SIG_IGN);
507 	signal(SIGALRM, SIG_IGN);
508 
509 	defproctitle();
510 
511 	setvbuf(fp, NULL, _IOLBF, 0);
512 	writeout(fp, "OK mailfromd callout server ready\r\n");
513 	while (fgets(buf, sizeof(buf), fp)) {
514 		struct mu_wordsplit ws;
515 		struct callout_command *cmd;
516 		int rc = 0;
517 
518 		if (trimcrlf(buf)) {
519 			mu_debug(MF_SOURCE_SAVSRV, MU_DEBUG_PROT,
520 				 ("%c: %s", longline ? '>' : 'C', buf));
521 			longline = 1;
522 			continue;
523 		}
524 
525 		if (longline) {
526 			writeout(fp, "NO bad input\r\n");
527 			longline = 0;
528 			continue;
529 		}
530 
531 		mu_debug(MF_SOURCE_SAVSRV, MU_DEBUG_PROT, ("C: %s", buf));
532 
533 		if (mu_wordsplit(buf, &ws, MU_WRDSF_DEFFLAGS)) {
534 			writeout(fp, "NO cannot parse line\r\n");
535 			continue;
536 		}
537 
538 		if (ws.ws_wordc == 0)
539 			writeout(fp, "NO empty command\r\n");
540 		else {
541 			cmd = find_command(ws.ws_wordv[0]);
542 			if (!cmd)
543 				writeout(fp, "NO unknown command\r\n");
544 			else if ((cmd->argmin && ws.ws_wordc < cmd->argmin)
545 				 || (cmd->argmax && ws.ws_wordc > cmd->argmax))
546 				writeout(fp, "NO invalid arguments\r\n");
547 			else
548 				rc = cmd->handler(fp, ws.ws_wordc,
549 						  ws.ws_wordv);
550 		}
551 		mu_wordsplit_free(&ws);
552 		if (rc)
553 			break;
554 	}
555 	fclose(fp);
556 
557 	/* Run queued verifications */
558 	run_queue(NULL, 1);
559 	return 0;
560 }
561 
562