/* This file is part of Mailfromd. Copyright (C) 2005-2011, 2015-2016 Sergey Poznyakoff This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libmf.h" #include "srvcfg.h" #include "callout.h" #include "mfdb.h" #include "callout-dbgmod.h" char *ehlo_domain; char *mailfrom_address; int enable_vrfy; char *session_id; /* FIXME: Rewrite I/O via mu_stream */ struct callout_command { const char *command; int argmin; int argmax; int (*handler) (FILE *, int, char **); }; enum callout_mode { cmode_mx_first, cmode_mx_only, cmode_host_only, cmode_host_first }; struct vrfy_queue { struct vrfy_queue *next; unsigned serial; enum callout_mode mode; char *hostname; smtp_io_t io; mf_status result; FILE *file; }; static struct vrfy_queue *head, *tail; static unsigned serial; int trimcrlf(char *buf) { size_t len = strlen(buf); if (len >= 1 && buf[len-1] == '\n') { buf[--len] = 0; if (len >= 1 && buf[len-1] == '\r') buf[--len] = 0; return 0; } return 1; } #define SPFX "S: " #define SPFXSIZ (sizeof(SPFX)-1) #define SBUFSIZ 512 static void writeout(FILE *fp, const char *fmt, ...) { va_list ap; va_start(ap, fmt); if (mu_debug_level_p(MF_SOURCE_SAVSRV, MU_DEBUG_PROT)) { static char fmtbuf[SPFXSIZ + SBUFSIZ + 3 + 1 + 1] = SPFX; size_t fmtlen = strlen(fmt); char *dots = NULL; int more = fmt[fmtlen - 1] != '\n'; if (fmtlen > SBUFSIZ) { fmtlen = SBUFSIZ; dots = "..."; } if (fmt[fmtlen - 1] == '%' && fmt[fmtlen - 2] != '%') fmtlen--; strncpy(fmtbuf + SPFXSIZ, fmt, fmtlen); fmtbuf[SPFXSIZ + fmtlen] = 0; trimcrlf(fmtbuf); if (dots) strcat(fmtbuf, dots); if (more) strcat(fmtbuf, "\\"); mu_diag_voutput(MU_LOG_DEBUG, fmtbuf, ap); } vfprintf(fp, fmt, ap); va_end(ap); } static void defproctitle() { if (session_id) mf_proctitle_format("callout server: %s", session_id); else mf_proctitle_format("callout server"); } static void vrfy_free(struct vrfy_queue *qp) { smtp_io_free(qp->io); free(qp->hostname); free(qp); } static int vrfy_del(unsigned num) { struct vrfy_queue *qp, *prev; if (!head) return -1; if (head->serial == num) { qp = head; head = head->next; if (!head) tail = NULL; vrfy_free(qp); return 0; } for (prev = head, qp = head->next; qp; prev = qp, qp = qp->next) { if (qp->serial == num) { prev->next = qp->next; vrfy_free(qp); return 0; } } return -1; } static void verify(struct vrfy_queue *qp, int hard) { mf_status rc; #define STATUS_FIXUP(code) \ do { \ if (hard && (code) == mf_timeout) \ (code) = mf_not_found; \ } while(0) smtp_io_set_timeouts(qp->io, hard ? smtp_timeout_hard : smtp_timeout_soft); switch (qp->mode) { case cmode_mx_first: mf_proctitle_format("callout(mx): %s: %010u:<%s>", smtp_io_id(qp->io), qp->serial, smtp_io_email(qp->io)); if (!hard) { rc = cache_get(smtp_io_email(qp->io)); if (rc != mf_failure) break; } rc = callout_standard(qp->io); STATUS_FIXUP(rc); cache_insert(smtp_io_email(qp->io), rc); break; case cmode_mx_only: mf_proctitle_format("callout(mx only): %s: %010u:<%s>@%s", smtp_io_id(qp->io), qp->serial, smtp_io_email(qp->io), qp->hostname); rc = callout_mx(qp->io, qp->hostname, NULL); STATUS_FIXUP(rc); break; case cmode_host_first: mf_proctitle_format("callout(host first): %s: %010u:<%s>@%s", smtp_io_id(qp->io), qp->serial, smtp_io_email(qp->io), qp->hostname); if (!hard) { rc = cache_get2(smtp_io_email(qp->io), qp->hostname); if (rc != mf_failure) break; } rc = callout_strict(qp->io, qp->hostname); STATUS_FIXUP(rc); cache_insert2(smtp_io_email(qp->io), qp->hostname, rc); break; case cmode_host_only: mf_proctitle_format("callout(host only): %s: %010u:<%s>@%s", smtp_io_id(qp->io), qp->serial, smtp_io_email(qp->io), qp->hostname); rc = callout_host(qp->io, qp->hostname); STATUS_FIXUP(rc); } qp->result = rc; } static void run_queue(FILE *fp, int hard) { struct vrfy_queue *qp; for (qp = head; qp; qp = qp->next) { qp->file = fp; verify(qp, hard); } defproctitle(); } static void savsrv_smtp_io_callback(void *data, const char *key, const char *value) { struct vrfy_queue *qp = data; if (qp->file) writeout(qp->file, "* %010u %s %s\r\n", qp->serial, key, value); } static char * getval(char *input) { char *p = strchr(input, '='); if (!p) return NULL; *p++ = 0; return p; } static struct vrfy_queue * addq(int argc, char **argv, const char **errp) { int i; struct vrfy_queue *qp = mu_alloc(sizeof(*qp)); char *email; char *host = NULL; char *ehlo = ehlo_domain; char *mailfrom = mailfrom_address; enum callout_mode mode = cmode_mx_first; email = argv[1]; for (i = 2; i < argc; i++) { char *attr = argv[i]; char *val = getval(attr); if (!val) { *errp = "syntax error"; return NULL; } if (!strcasecmp(attr, "host")) { host = val; } else if (!strcasecmp(attr, "mode")) { if (!strcmp(val, "mxfirst") || !strcmp(val, "default")) mode = cmode_mx_first; else if (!strcmp(val, "mxonly")) mode = cmode_mx_only; else if (!strcmp(val, "hostonly")) mode = cmode_host_only; else if (!strcmp(val, "hostfirst")) mode = cmode_host_first; } else if (!strcasecmp(attr, "ehlo")) { ehlo = val; } else if (!strcasecmp(attr, "mailfrom")) { mailfrom = val; } else { *errp = "unknow attribute"; return NULL; } } switch (mode) { case cmode_host_first: case cmode_host_only: case cmode_mx_only: if (!host) { *errp = "mode requires host"; return NULL; } break; case cmode_mx_first: break; } qp->serial = serial++; qp->result = mf_temp_failure; /* FIXME */ qp->io = smtp_io_create(session_id ? session_id : email, smtp_timeout_hard, savsrv_smtp_io_callback, qp); smtp_io_setup_callout(qp->io, email, ehlo, mailfrom); switch (mode) { case cmode_host_first: case cmode_host_only: case cmode_mx_only: qp->hostname = mu_strdup(host); break; case cmode_mx_first: qp->hostname = NULL; break; } qp->mode = mode; qp->next = NULL; if (tail) tail->next = qp; else head = qp; tail = qp; return qp; } int cmd_vrfy(FILE *fp, int argc, char **argv) { const char *errp = "syntax error"; struct vrfy_queue *qp = addq(argc, argv, &errp); if (qp) writeout(fp, "OK %010u\r\n", qp->serial); else writeout(fp, "NO %s\r\n", errp); return 0; } int cmd_get(FILE *fp, int argc, char **argv) { int i; for (i = 1; i < argc; i++) { char *attr = argv[i]; char *val; if (!strcasecmp(attr, "ehlo")) val = ehlo_domain; else if (!strcasecmp(attr, "mailfrom")) val = mailfrom_address; /* FIXME: Timeouts */ else val = NULL; if (val) writeout(fp, "* %s=%s\r\n", attr, val); } writeout(fp, "OK\r\n"); return 0; } int cmd_sid(FILE *fp, int argc, char **argv) { if (session_id) free(session_id); session_id = mu_strdup(argv[1]); defproctitle(); writeout(fp, "OK\r\n"); return 0; } int cmd_timeout(FILE *fp, int argc, char **argv) { time_t to[SMTP_NUM_TIMEOUT]; int i; for (i = 1; i < argc; i++) { char *p; unsigned long n = strtoul(argv[i], &p, 10); if (*p) { writeout(fp, "NO syntax error in #%d\r\n", i); return 0; } to[i-1] = (time_t) n; } memcpy(smtp_timeout_soft, to, sizeof(to)); writeout(fp, "OK timeouts set\r\n"); return 0; } int cmd_run(FILE *fp, int argc, char **argv) { struct vrfy_queue *qp, *prev; run_queue(fp, 0); writeout(fp, "OK"); for (qp = head, prev = NULL; qp; ) { struct vrfy_queue *next = qp->next; mf_status result = (qp->result == mf_timeout) ? mf_temp_failure : qp->result; writeout(fp, " %010u=%s", qp->serial, mf_status_str(result)); if (qp->result != mf_failure && qp->result != mf_timeout) { if (qp == tail) tail = prev; vrfy_free(qp); if (prev) prev->next = next; else head = next; } else prev = qp; qp = next; } writeout(fp, "\r\n"); return 0; } int cmd_drop(FILE *fp, int argc, char **argv) { if (strcasecmp(argv[1], "ALL") == 0) { struct vrfy_queue *qp; for (qp = head; qp; ) { struct vrfy_queue *next = qp->next; vrfy_free(qp); qp = next; } head = tail = NULL; } else { char *p; unsigned num = strtoul(argv[1], &p, 10); if (*p) writeout(fp, "NO syntax error\r\n"); else if (vrfy_del(num)) writeout(fp, "NO entry not found\r\n"); else writeout(fp, "OK\r\n"); } return 0; } int cmd_quit(FILE *fp, int argc, char **argv) { writeout(fp, "OK bye\r\n"); return 1; } static struct callout_command callout_command_tab[] = { { "VRFY", 2, 0, cmd_vrfy }, { "GET", 2, 0, cmd_get }, /* FIXME: SET */ { "SID", 2, 2, cmd_sid }, { "TIMEOUT", SMTP_NUM_TIMEOUT+1, SMTP_NUM_TIMEOUT+1, cmd_timeout }, { "RUN", 1, 1, cmd_run }, { "QUIT", 1, 1, cmd_quit }, { "DROP", 2, 2, cmd_drop }, { NULL } }; static struct callout_command * find_command(const char *input) { struct callout_command *cmd; for (cmd = callout_command_tab; cmd->command; cmd++) { if (strcasecmp(cmd->command, input) == 0) return cmd; } return NULL; } int callout_session_server(const char *id, int fd, struct sockaddr const *sa, socklen_t len, void *server_data, void *srvman_data) { FILE *fp = fdopen(fd, "w+"); char buf[1024]; size_t size = 0; int longline = 0; signal(SIGPIPE, SIG_IGN); signal(SIGALRM, SIG_IGN); defproctitle(); setvbuf(fp, NULL, _IOLBF, 0); writeout(fp, "OK mailfromd callout server ready\r\n"); while (fgets(buf, sizeof(buf), fp)) { struct mu_wordsplit ws; struct callout_command *cmd; int rc = 0; if (trimcrlf(buf)) { mu_debug(MF_SOURCE_SAVSRV, MU_DEBUG_PROT, ("%c: %s", longline ? '>' : 'C', buf)); longline = 1; continue; } if (longline) { writeout(fp, "NO bad input\r\n"); longline = 0; continue; } mu_debug(MF_SOURCE_SAVSRV, MU_DEBUG_PROT, ("C: %s", buf)); if (mu_wordsplit(buf, &ws, MU_WRDSF_DEFFLAGS)) { writeout(fp, "NO cannot parse line\r\n"); continue; } if (ws.ws_wordc == 0) writeout(fp, "NO empty command\r\n"); else { cmd = find_command(ws.ws_wordv[0]); if (!cmd) writeout(fp, "NO unknown command\r\n"); else if ((cmd->argmin && ws.ws_wordc < cmd->argmin) || (cmd->argmax && ws.ws_wordc > cmd->argmax)) writeout(fp, "NO invalid arguments\r\n"); else rc = cmd->handler(fp, ws.ws_wordc, ws.ws_wordv); } mu_wordsplit_free(&ws); if (rc) break; } fclose(fp); /* Run queued verifications */ run_queue(NULL, 1); return 0; }