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