/* This file is part of Mailfromd.
Copyright (C) 2005-2020 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--;
memcpy(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];
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;
}