/* This file is part of Mailfromd.
Copyright (C) 2008 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 "xalloc.h"
#include "libmf.h"
/* FIXME */
#include
const char *mu_umaxtostr (unsigned slot, uintmax_t val);
#include "sm/error.h"
#include "sm/smreplycodes.h"
#include "sm/pmilter.h"
#include "sm/pmfdef.h"
#include "sm/pmfapi.h"
#include "pdbg.h"
const char *program_version = "pmult (" PACKAGE_STRING ")";
const char *package_bugreport = "<" PACKAGE_BUGREPORT ">";
char *portspec; /* Communication socket */
int log_to_stderr; /* Use stderr for logging */
char *log_tag; /* override mu_log_tag */
mu_log_level_t debug_level; /* Debug verbosity level */
mu_debug_t pmult_debug; /* Debugging object */
static pthread_mutex_t pmult_debug_mutex = PTHREAD_MUTEX_INITIALIZER;
char *pidfile; /* pidfile name */
int no_sig_handler; /* Disable signal handler */
struct pmult_client
{
int type; /* Type, unused so far */
char *name;
struct timeval timeout[GACOPYZ_TO_COUNT];
char *url;
int logmask;
};
/* List of configured clients: */
mu_list_t /* of struct pmult_client */ client_list;
enum pmult_msg_state
{
pmult_msg_state_initial,
pmult_msg_state_headers,
pmult_msg_state_cr1,
pmult_msg_state_crlf1,
pmult_msg_state_cr2,
pmult_msg_state_body
};
struct pmult_priv_data
{
mu_debug_t debug;
mu_list_t /* of gacopyz_srv_t */ srvlist;
pthread_mutex_t mutex;
char *seid;
char *seid_c;
char *taid;
unsigned nrcpt;
unsigned nbadrcpts;
mu_stream_t hdrstream;
enum pmult_msg_state state;
mu_header_t hdr;
};
#define PRIV_SEID(p) ((p)->seid ? (p)->seid : "")
#define PRIV_SEID_C(p) ((p)->seid_c ? (p)->seid_c : pmult_seid_c (p))
#define PRIV_LOCK(p) pthread_mutex_lock (&(p)->mutex)
#define PRIV_UNLOCK(p) pthread_mutex_unlock (&(p)->mutex)
static char *
pmult_seid_c (struct pmult_priv_data *p)
{
if (p->seid)
{
p->seid_c = malloc (strlen (p->seid) + 2);
if (p->seid_c)
{
strcpy (p->seid_c, p->seid);
strcat (p->seid_c, ":");
return p->seid_c;
}
}
return "";
}
static pthread_mutex_t pmult_mutex = PTHREAD_MUTEX_INITIALIZER;
#define protect() pthread_mutex_lock (&pmult_mutex)
#define unprotect() pthread_mutex_unlock (&pmult_mutex)
/* Logging */
void
log_setup (int want_stderr)
{
mu_debug_t debug;
mu_diag_get_debug (&debug);
if (log_tag)
mu_log_tag = log_tag;
if (!want_stderr)
{
openlog (MU_LOG_TAG (), LOG_PID, mu_log_facility);
gacopyz_set_logger (gacopyz_syslog_log_printer);
mu_debug_set_print (debug, mu_diag_syslog_printer, NULL);
mu_debug_default_printer = mu_debug_syslog_printer;
}
else
{
gacopyz_set_logger (gacopyz_stderr_log_printer);
mu_debug_default_printer = mu_debug_stderr_printer;
}
}
static int
stderr_closed_p()
{
int fd = dup(0);
if (fd < 0)
return 1;
close(fd);
return fd <= 2;
}
static int
_cb_client_type (mu_debug_t debug, void *data, char *arg)
{
if (strcmp (arg, "milter") == 0)
/* dobrze */;
else if (strcmp (arg, "pmilter") == 0)
mu_cfg_format_error (debug, MU_DEBUG_ERROR,
_("client type %s is not supported yet"),
arg);
else
mu_cfg_format_error (debug, MU_DEBUG_ERROR,
_("unknown client type %s"),
arg);
return 0;
}
static int
cb_timeout (struct timeval *pt, mu_debug_t debug, char *arg)
{
const char *endp;
time_t t;
if (parse_time_interval (arg, &t, &endp))
{
mu_cfg_format_error (debug, MU_DEBUG_ERROR,
_("unrecognized time format (near `%s')"),
endp);
return 1;
}
pt->tv_usec = 0;
pt->tv_sec = t;
return 0;
}
static int
_cb_write_timeout (mu_debug_t debug, void *data, char *arg)
{
struct pmult_client *clt = data;
return cb_timeout (&clt->timeout[GACOPYZ_TO_WRITE], debug, arg);
}
static int
_cb_read_timeout (mu_debug_t debug, void *data, char *arg)
{
struct pmult_client *clt = data;
return cb_timeout (&clt->timeout[GACOPYZ_TO_READ], debug, arg);
}
static int
_cb_eom_timeout (mu_debug_t debug, void *data, char *arg)
{
struct pmult_client *clt = data;
return cb_timeout (&clt->timeout[GACOPYZ_TO_EOM], debug, arg);
}
static int
_cb_connect_timeout (mu_debug_t debug, void *data, char *arg)
{
struct pmult_client *clt = data;
return cb_timeout (&clt->timeout[GACOPYZ_TO_CONNECT], debug, arg);
}
static int
_cb_log_level (mu_debug_t debug, void *data, char *arg)
{
struct pmult_client *clt = data;
int argc, i;
char **argv;
int rc;
rc = mu_argcv_get_np (arg, strlen (arg), ",", NULL, 0, &argc, &argv, NULL);
if (rc)
{
mu_cfg_format_error (debug, MU_DEBUG_ERROR,
"mu_argcv_get: %s", mu_strerror (rc));
return 1;
}
for (i = 0; i < argc; i++)
{
char *p = argv[i];
int lev;
int revert = 0;
int upto = 0;
if (*p == '!')
{
p++;
revert = 1;
}
if (*p == '<')
{
p++;
upto = 1;
}
lev = gacopyz_string_to_log_level (p);
if (lev == -1)
{
mu_cfg_format_error (debug, MU_DEBUG_ERROR,
_("Invalid log level: %s"), arg);
return 1;
}
if (revert)
{
if (upto)
clt->logmask &= ~SMI_LOG_UPTO (lev);
else
clt->logmask &= ~SMI_LOG_FROM (lev);
}
else
{
if (upto)
clt->logmask |= SMI_LOG_UPTO (lev);
else
clt->logmask |= SMI_LOG_FROM (lev);
}
}
mu_argcv_free (argc, argv);
return 0;
}
struct mu_cfg_param client_cfg_param[] = {
{ "type", mu_cfg_callback, NULL, 0, _cb_client_type,
N_("Set remote milter type. Only `milter' is understood so far."),
N_("{milter [version: number]|pmilter}") },
{ "url", mu_cfg_string, NULL, mu_offsetof(struct pmult_client, url), NULL,
N_("Set remote client URL.") },
{ "write-timeout", mu_cfg_callback, NULL, 0, _cb_write_timeout,
N_("Set write timeout."),
N_("time") },
{ "read-timeout", mu_cfg_callback, NULL, 0, _cb_read_timeout,
N_("Set read timeout."),
N_("time") },
{ "eom-timeout", mu_cfg_callback, NULL, 0, _cb_eom_timeout,
N_("Set timeout for EOM."),
N_("time") },
{ "connect-timeout", mu_cfg_callback, NULL, 0, _cb_connect_timeout,
N_("Set connect timeout."),
N_("time") },
{ "log-level", mu_cfg_callback, NULL, 0, _cb_log_level,
N_("Set log verbosity level. Arg is a list of items separated by commas "
"or whitespace. Each item is a log level optionally prefixed with `!' "
"to indicate `any level except this', or '<', meaning `all levels up "
"to and including this'. Log levels in order of increasing priority "
"are: debug, info, warn, err, fatal."),
N_("arg: list") },
{ NULL }
};
static int
_cb_portspec (mu_debug_t debug, void *data, char *arg)
{
char **pptr = data, *ptr;
char *proto = NULL;
char *port = NULL;
char *path = NULL;
size_t len;
if (gacopyz_parse_connection (arg, &proto, &port, &path))
{
mu_cfg_format_error (debug, MU_DEBUG_ERROR, _("Invalid URL: %s"), arg);
return 1;
}
if (!proto)
{
if (port)
{
mu_cfg_format_error (debug, MU_DEBUG_ERROR,
_("Invalid URL: %s"), arg);
return 1;
}
ptr = xstrdup (path);
}
else
{
len = strlen (proto) + 1
+ (port ? strlen (port) + 1 : 0) + strlen (path) + 1;
ptr = xmalloc (len);
strcpy (ptr, proto);
strcat (ptr, ":");
if (port)
{
strcat (ptr, port);
strcat (ptr, "@");
}
strcat (ptr, path);
}
free (path);
free (port);
free (proto);
*pptr = ptr;
return 0;
}
static int
_cb_debug (mu_debug_t debug, void *data, char *arg)
{
int rc;
rc = mu_debug_level_from_string (arg, &debug_level, debug);
if (rc)
return 0;
if (!pmult_debug)
mu_debug_create (&pmult_debug, NULL);
mu_debug_set_level (pmult_debug, debug_level);
return 0;
}
struct mu_cfg_param pmult_cfg_param[] = {
{ "listen", mu_cfg_callback, &portspec, 0, _cb_portspec,
N_("Listen for milter requests on the given URL."),
N_("url") },
{ "client", mu_cfg_section },
{ "debug", mu_cfg_callback, NULL, 0, _cb_debug,
N_("Set debug verbosity level.") },
{ "pidfile", mu_cfg_string, &pidfile, 0, NULL,
N_("Write PID to this file.") },
{ NULL }
};
static void
pmult_client_free (struct pmult_client *clt)
{
free (clt->name);
free (clt);
}
static int
client_block_begin (mu_debug_t debug, char *name, void **section_data)
{
extern struct timeval default_gacopyz_timeout[GACOPYZ_TO_COUNT];
struct pmult_client *clt = xcalloc (1, sizeof *clt);
clt->name = name ? xstrdup (name) : "";
memcpy (clt->timeout, default_gacopyz_timeout, sizeof clt->timeout);
*section_data = clt;
return 0;
}
static int
client_block_end (mu_debug_t debug, struct pmult_client *clt)
{
gacopyz_srv_t gsrv;
int rc = gacopyz_srv_create (&gsrv, clt->name, clt->url, clt->logmask);
if (rc)
{
pmult_client_free (clt);
return 1; /* FIXME: error message */
}
gacopyz_srv_destroy (&gsrv);
if (!client_list)
MU_ASSERT (mu_list_create (&client_list));
mu_list_append (client_list, clt);
return 0;
}
static int
client_section_parser (enum mu_cfg_section_stage stage,
const mu_cfg_node_t *node,
const char *section_label, void **section_data,
void *call_data,
mu_cfg_tree_t *tree)
{
switch (stage)
{
case mu_cfg_section_start:
return client_block_begin (tree->debug, node->tag_label, section_data);
case mu_cfg_section_end:
return client_block_end (tree->debug,
(struct pmult_client *)*section_data);
}
return 0;
}
static void
pmult_cfg_init ()
{
struct mu_cfg_section *section;
if (mu_create_canned_section ("client", §ion))
exit (EX_SOFTWARE);
section->parser = client_section_parser;
section->label = N_("ident");
mu_cfg_section_add_params (section, client_cfg_param);
}
/* Command line parsing */
static char doc[] = N_("pmult -- pmilter multiplexer");
static char args_doc[] = "";
enum {
OPTION_URL = 256,
OPTION_SYSLOG,
OPTION_LOG_TAG,
OPTION_NO_SIGNAL_HANDLER,
};
static struct argp_option options[] = {
{ "url", OPTION_URL, N_("URL"), 0,
N_("Listen on the given URL"), 0 },
{ "syslog", OPTION_SYSLOG, NULL, 0,
N_("Log to syslog (default)"), },
{ "stderr", 's', NULL, 0,
N_("Log to stderr"), },
{ "log-tag", OPTION_LOG_TAG, N_("STRING"), 0,
N_("Set the identifier used in syslog messages to STRING"), },
{ "debug", 'x', N_("LEVEL"), 0,
N_("Set debug verbosity level.") },
{ "no-signal-handler", OPTION_NO_SIGNAL_HANDLER, NULL, 0,
N_("Disable signal handling in the main thread (use for debugging).") },
{ NULL }
};
static error_t
parse_opt (int key, char *arg, struct argp_state *state)
{
static struct mu_argp_node_list lst;
switch (key)
{
case OPTION_URL:
mu_argp_node_list_new (&lst, "listen", arg);
break;
case OPTION_SYSLOG:
log_to_stderr = 0;
break;
case OPTION_NO_SIGNAL_HANDLER:
no_sig_handler = 1;
break;
case 's':
log_to_stderr = 1;
break;
case 'x':
mu_argp_node_list_new (&lst, "debug", arg);
break;
case OPTION_LOG_TAG:
log_tag = arg;
break;
case ARGP_KEY_INIT:
mu_argp_node_list_init (&lst);
break;
case ARGP_KEY_FINI:
mu_argp_node_list_finish (&lst, NULL, NULL);
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
static const char *capa[] = {
"common",
"debug",
"logging",
NULL
};
static struct argp argp = {
options,
parse_opt,
args_doc,
doc,
NULL,
NULL,
NULL
};
#define SM_ONERROR_ACTION(expr,onerr) do \
{ \
sm_ret_T ret = expr; \
if (sm_is_err (ret)) \
{ \
char *cp = smerr2txt (ret); \
if (cp) \
mu_error ("%s:%d: " #expr " failed: %s", \
__FILE__, __LINE__, cp); \
else \
mu_error ("%s:%d: " #expr " failed: %#x", \
__FILE__, __LINE__, ret); \
onerr; \
} \
} \
while (0)
#define SM_VERBOSE(expr) SM_ONERROR_ACTION(expr,)
#define SM_CHECK(expr) SM_ONERROR_ACTION(expr,return ret)
#define SM_ASSERT(expr) SM_ONERROR_ACTION(expr, exit (EX_CONFIG))
#define MSG_TEMPFAIL "451 4.3.2 Please try again later\r\n"
#define MSG_REJECT "550 5.7.1 Command rejected\r\n"
#define MSG_SHUTDOWN "421 4.7.0 pmult closing connection\r\n"
const char *
hdrcommname (int rc)
{
switch (rc)
{
case SMFIR_CHGHEADER:
return "chgheader";
case SMFIR_ADDHEADER:
return "addheader";
case SMFIR_INSHEADER:
return "insheader";
}
return "unknown";
}
static int
get_mod_index (struct pmult_priv_data *p, const char *header_name,
size_t pos, size_t *pidx)
{
size_t i, count;
const char *name;
if (mu_header_get_field_count (p->hdr, &count))
return 1;
for (i = 1; i <= count; i++)
{
if (mu_header_sget_field_name (p->hdr, i, &name))
continue;
if (strcmp (name, header_name) == 0 && --pos == 0)
{
*pidx = i - 1;
return 0;
}
}
return 1;
}
static int
cb_reply (gacopyz_srv_t gsrv, int cmd, int rcmd, void *data)
{
char *buf;
size_t size;
char *cp;
char *header;
uint type;
uint32_t idx = 0;
pmse_ctx_P pmse_ctx = data;
struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
switch (rcmd)
{
case SMFIR_CHGHEADER:
case SMFIR_ADDHEADER:
case SMFIR_INSHEADER:
gacopyz_srv_reply (gsrv, &buf, &size);
if (rcmd != SMFIR_ADDHEADER)
{
idx = ntohl (*(uint32_t*)buf);
buf += sizeof (uint32_t);
}
cp = memchr (buf, 0, size);
if (!cp)
{
mu_diag_output (MU_DIAG_NOTICE, "Malformed CHGHEADER command");
break;
}
cp++;
PMU_DEBUG4 (p->debug, MU_DEBUG_TRACE2,
"%s=%u '%s','%s'\n",
hdrcommname (rcmd), idx, buf, cp);
header = malloc (size + 5);
if (!header)
{
mu_error ("%s", mu_strerror (ENOMEM));
return SMTP_R_ACCEPT;
}
strcpy (header, buf);
strcat (header, ": ");
strcat (header, cp);
strcat (header, "\r\n");
switch (rcmd)
{
case SMFIR_CHGHEADER:
{
/* Emulate milter semantics */
size_t index;
type = (*cp == 0) ? SM_HDRMOD_T_REMOVE : SM_HDRMOD_T_REPLACE;
if (get_mod_index (p, buf, idx, &index))
{
PMU_DEBUG1 (p->debug, MU_DEBUG_TRACE1,
"no such header: %s\n", buf);
return 0;
}
idx = index;
}
break;
case SMFIR_ADDHEADER:
type = SM_HDRMOD_T_APPEND;
break;
case SMFIR_INSHEADER:
type = SM_HDRMOD_T_INSERT;
}
SM_VERBOSE (sm_pmfi_hdr_mod (pmse_ctx, type, idx, header));
free (header);
}
return 0;
}
#define CRLF "\r\n"
static char *
trailcrlf (char **pbuf)
{
char *buf = *pbuf;
char *retp = NULL;
int len;
if (!buf)
*pbuf = CRLF;
else if ((len = strlen (buf)) < 2 || memcmp (buf + len - 2, CRLF, 2))
{
retp = malloc (len + 3);
if (!retp)
{
mu_error ("%s", mu_strerror (ENOMEM));
*pbuf = MSG_TEMPFAIL;
}
strcat (strcpy (retp, buf), CRLF);
*pbuf = retp;
}
return retp;
}
static int
pmult_std_reply (struct pmult_priv_data *p, pmse_ctx_P pmse_ctx,
gacopyz_srv_t gsrv, int rc,
const char *ident, const char *arg)
{
char *buf, *tmp;
size_t size;
int status;
if (!arg)
arg = "";
switch (rc)
{
case SMFIR_CONTINUE:
break;
case SMFIR_REPLYCODE:
gacopyz_srv_reply (gsrv, &buf, &size);
PMU_DEBUG3 (p->debug, MU_DEBUG_TRACE2,
"%s=%s, reply=%s\n",
ident, arg, buf);
tmp = trailcrlf (&buf);
SM_VERBOSE (sm_pmfi_setreply (pmse_ctx, tmp));
status = (buf[0] == '4') ? SMTP_R_TEMP : SMTP_R_PERM;
free (tmp);
return status;
case SMFIR_REJECT:
PMU_DEBUG3 (p->debug, MU_DEBUG_TRACE2,
"%s=%s, reply=%s\n",
ident, arg, MSG_REJECT);
SM_VERBOSE (sm_pmfi_setreply (pmse_ctx, MSG_REJECT));
return SMTP_R_PERM;
case SMFIR_DISCARD:
PMU_DEBUG2 (p->debug, MU_DEBUG_TRACE2,
"%s=%s, discard\n",
ident, arg);
return SMTP_R_DISCARD;
case SMFIR_TEMPFAIL:
PMU_DEBUG3 (p->debug, MU_DEBUG_TRACE2,
"%s=%s, reply=%s\n",
ident, arg, MSG_TEMPFAIL);
SM_VERBOSE (sm_pmfi_setreply (pmse_ctx, MSG_TEMPFAIL));
return SMTP_R_TEMP;
case SMFIR_SHUTDOWN:
PMU_DEBUG3 (p->debug, MU_DEBUG_TRACE2,
"%s=%s, reply=%s\n",
ident, arg, MSG_SHUTDOWN);
SM_VERBOSE (sm_pmfi_setreply (pmse_ctx, MSG_SHUTDOWN));
return SMTP_R_SSD;
case SMFIR_ADDRCPT:
gacopyz_srv_reply (gsrv, &buf, &size);
PMU_DEBUG3 (p->debug, MU_DEBUG_TRACE2,
"%s=%s, addrcpt=%s\n",
ident, arg, buf);
SM_VERBOSE (sm_pmfi_rcpt_add (pmse_ctx, buf, NULL));
break;
case SMFIR_DELRCPT:
gacopyz_srv_reply (gsrv, &buf, &size);
PMU_DEBUG3 (p->debug, MU_DEBUG_TRACE2,
"%s=%s, delrcpt=%s\n",
ident, arg, buf);
/* FIXME: Index is always 0. Should it be 1? */
SM_VERBOSE (sm_pmfi_rcpt_del (pmse_ctx, buf, 0));
break;
case SMFIR_CHGHEADER:
case SMFIR_ADDHEADER:
case SMFIR_INSHEADER:
break;
case SMFIR_REPLBODY:
/* FIXME */
default:
if (isascii (rc) && isprint (rc))
mu_diag_output (MU_DIAG_WARNING, _("Unsupported reply code: %c (%d)"),
rc, rc);
else
mu_diag_output (MU_DIAG_WARNING, _("Unsupported reply code: (%d)"),
rc);
}
return SMTP_R_CONT;
}
typedef sfsistat_T (*pmult_runfun_t) (pmse_ctx_P, gacopyz_srv_t, void *);
static sfsistat_T
pmult_runlist0 (struct pmult_priv_data *p, pmult_runfun_t runfun,
pmse_ctx_P pmse_ctx, void *data, char **macros)
{
sfsistat_T rc;
mu_iterator_t itr = NULL;
mu_list_get_iterator (p->srvlist, &itr);
for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
mu_iterator_next (itr))
{
int i;
gacopyz_srv_t gsrv;
mu_iterator_current (itr, (void**)&gsrv);
if (macros)
for (i = 0; macros[i]; i += 2)
gacopyz_srv_define_macro (gsrv, macros[i], macros[i+1]);
rc = runfun (pmse_ctx, gsrv, data);
if (rc != SMTP_R_CONT && rc != SMTP_R_OK)
break;
}
mu_iterator_destroy (&itr);
return rc;
}
static sfsistat_T
pmult_runlist (struct pmult_priv_data *p, pmult_runfun_t runfun,
pmse_ctx_P pmse_ctx, void *data, char **macros)
{
sfsistat_T rc;
PRIV_LOCK (p);
rc = pmult_runlist0 (p, runfun, pmse_ctx, data, macros);
PRIV_UNLOCK (p);
return rc;
}
void
pmult_free (struct pmult_priv_data *p)
{
if (!p)
return;
mu_list_destroy (&p->srvlist);
free (p->taid);
free (p->seid);
free (p->seid_c);
mu_stream_destroy (&p->hdrstream, NULL);
mu_header_destroy (&p->hdr, mu_header_get_owner (p->hdr));
mu_debug_destroy (&p->debug, NULL);
pthread_mutex_destroy (&p->mutex);
free (p);
}
void
pmult_clear (struct pmult_priv_data *p)
{
PRIV_LOCK (p);
mu_stream_destroy (&p->hdrstream, NULL);
mu_header_destroy (&p->hdr, mu_header_get_owner (p->hdr));
p->state = pmult_msg_state_initial;
p->nrcpt = 0;
p->nbadrcpts = 0;
PRIV_UNLOCK (p);
}
void
pmult_shutdown (pmse_ctx_P pmse_ctx, struct pmult_priv_data *p)
{
mu_iterator_t itr;
if (!p)
return;
PRIV_LOCK (p);
mu_list_get_iterator (p->srvlist, &itr);
for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
mu_iterator_next (itr))
{
gacopyz_srv_t gsrv;
mu_iterator_current (itr, (void**)&gsrv);
gacopyz_srv_quit (gsrv);
gacopyz_srv_close (gsrv);
gacopyz_srv_destroy (&gsrv);
}
mu_iterator_destroy (&itr);
sm_pmfi_set_ctx_se (pmse_ctx, NULL);
PRIV_UNLOCK (p);
pmult_free (p);
}
static void
version (FILE *stream, struct argp_state *state)
{
mailfromd_version ("pmult", stream);
}
static sm_ret_T
pmult_negotiate (pmss_ctx_P pmss_ctx,
uint32_t srv_cap, uint32_t srv_fct, uint32_t srv_feat,
uint32_t srv_misc, uint32_t *pm_cap, uint32_t *pm_fct,
uint32_t *pm_feat, uint32_t *pm_misc)
{
SM_CHECK (sm_pmfi_setmaclist (pmss_ctx, PM_SMST_CONNECT, PMM_SEID, PMM_END));
SM_CHECK (sm_pmfi_setmaclist (pmss_ctx, PM_SMST_MAIL, PMM_MAIL_TAID,
PMM_END));
return SM_SUCCESS;
}
static sfsistat_T
pmult_connect (pmse_ctx_P pmse_ctx, const char *hostname,
sm_sockaddr_T *hostaddr)
{
mu_iterator_t itr;
struct pmult_priv_data *p;
mu_debug_t dbg = NULL;
int status = SMTP_R_CONT;
int rc;
char *tmp = NULL;
char *client_addr = NULL, *client_port = NULL;
if (debug_level)
{
mu_debug_create (&dbg, NULL);
mu_debug_set_level (dbg, debug_level);
}
if (mu_debug_check_level (dbg, MU_DEBUG_TRACE1))
{
char *p = mu_sockaddr_to_astr (&hostaddr->sa, sizeof *hostaddr);
__PMU_DEBUG2 (dbg, MU_DEBUG_TRACE1,
"Connect from: %s, address %s\n", hostname, p);
free (p);
}
p = calloc (1, sizeof *p);
if (!p)
{
mu_error ("%s: accept", mu_strerror (ENOMEM));
mu_debug_destroy (&dbg, NULL);
return SMTP_R_ACCEPT;
}
p->debug = dbg;
pthread_mutex_init (&p->mutex, NULL);
sm_pmfi_getmac (pmse_ctx, PMM_SEID, &tmp);
p->seid = strdup (tmp);
rc = mu_list_create (&p->srvlist);
if (rc)
{
mu_error ("%smu_list_create: %s", PRIV_SEID_C (p), mu_strerror (rc));
mu_debug_destroy (&dbg, NULL);
free (p);
return SMTP_R_ACCEPT;
}
protect ();
mu_list_get_iterator (client_list, &itr);
client_addr = strdup (inet_ntoa (hostaddr->sin.sin_addr));
client_port = strdup (mu_umaxtostr (0, ntohs (hostaddr->sin.sin_port)));
unprotect ();
PRIV_LOCK (p);
for (mu_iterator_first (itr); !mu_iterator_is_done (itr);
mu_iterator_next (itr))
{
struct pmult_client *clt;
gacopyz_srv_t gsrv;
mu_iterator_current (itr, (void**)&clt);
gacopyz_srv_create (&gsrv, clt->name, clt->url, clt->logmask);
gacopyz_srv_set_callback (gsrv, cb_reply);
gacopyz_srv_set_callback_data (gsrv, pmse_ctx);
gacopyz_srv_set_all_timeouts (gsrv, clt->timeout);
if (gacopyz_srv_connect (gsrv) != MI_SUCCESS)
{
mu_error (_("%sFailed to connect to %s (milter %s)"),
PRIV_SEID_C (p), clt->url, clt->name);
gacopyz_srv_destroy (&gsrv);
continue;
}
gacopyz_srv_negotiate (gsrv);
gacopyz_srv_define_macro (gsrv, "r", "SMTP");
gacopyz_srv_define_macro (gsrv, "i", PRIV_SEID (p));
if (client_addr)
gacopyz_srv_define_macro (gsrv, "client_addr", client_addr);
gacopyz_srv_define_macro (gsrv, "client_name", hostname);
if (client_port)
gacopyz_srv_define_macro (gsrv, "client_port", client_port);
/* FIXME: client_ptr, client_resolve */
rc = gacopyz_srv_conn (gsrv, hostname, &hostaddr->sa);
status = pmult_std_reply (p, pmse_ctx, gsrv, rc, "connect", hostname);
if (status != SMTP_R_CONT)
break;
mu_list_append (p->srvlist, gsrv);
}
free (client_addr);
free (client_port);
protect ();
mu_iterator_destroy (&itr);
unprotect ();
PRIV_UNLOCK (p);
if (status == SMTP_R_CONT)
sm_pmfi_set_ctx_se (pmse_ctx, p);
else
pmult_free (p);
return status;
}
static sm_ret_T
pmult_close (pmse_ctx_P pmse_ctx)
{
struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
if (p)
{
PMU_DEBUG1 (p->debug, MU_DEBUG_TRACE1, "%sClosing connection\n",
PRIV_SEID_C (p));
pmult_shutdown (pmse_ctx, p);
}
return SM_SUCCESS;
}
static sfsistat_T
rf_helo (pmse_ctx_P pmse_ctx, gacopyz_srv_t gsrv, void *data)
{
struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
const char *helohost = data;
int rc = gacopyz_srv_helo (gsrv, helohost);
return pmult_std_reply (p, pmse_ctx, gsrv, rc, "helo", helohost);
}
static sfsistat_T
pmult_helo (pmse_ctx_P pmse_ctx, const char *helohost, bool ehlo)
{
struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
char *kv[3];
PMU_DEBUG2 (p->debug, MU_DEBUG_TRACE1, "%sHELO %s\n",
PRIV_SEID_C (p), helohost);
kv[0] = "s";
kv[1] = (char*) helohost;
kv[2] = NULL;
return pmult_runlist (p, rf_helo, pmse_ctx, (void*) helohost, kv);
}
struct env_arg
{
char *ident;
int (*srvfun) (gacopyz_srv_t, char **);
char **argv;
};
/* Common for envmail and envrcpt */
static sfsistat_T
rf_envfun (pmse_ctx_P pmse_ctx, gacopyz_srv_t gsrv, void *data)
{
struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
struct env_arg *x = data;
int rc = x->srvfun (gsrv, x->argv);
return pmult_std_reply (p, pmse_ctx, gsrv, rc, x->ident, x->argv[0]);
}
static sfsistat_T
pmult_mail (pmse_ctx_P pmse_ctx, const char *mail, char **argv)
{
int rc, i, n;
mu_address_t addr;
struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
char *kv[9];
struct env_arg x;
sfsistat_T status;
if (mu_debug_check_level (p->debug, MU_DEBUG_TRACE1))
{
int i;
__PMU_DEBUG2 (p->debug, MU_DEBUG_TRACE1, "%sMAIL FROM: %s",
PRIV_SEID_C (p), mail);
if (argv)
{
for (i = 0; argv[i]; i++)
__PMU_DEBUG1 (p->debug, MU_DEBUG_TRACE1, " %s", argv[i]);
}
PMU_DEBUG (p->debug, MU_DEBUG_TRACE1, "\n");
}
/* Fill in the macro array */
kv[0] = "f";
rc = mu_address_create (&addr, mail);
if (rc)
kv[1] = strdup (mail);
else
{
mu_address_aget_email (addr, 1, &kv[1]);
mu_address_destroy (&addr);
}
kv[2] = "ntries";
kv[3] = "0";
kv[4] = "nrcpts";
kv[5] = "0";
kv[6] = "nbadrcpts";
kv[7] = "0";
kv[8] = NULL;
/* Count arguments and allocate env array */
if (argv)
{
for (n = 0; argv[n]; n++)
;
}
else
n = 0;
x.argv = calloc (n + 2, sizeof (x.argv[0]));
if (!x.argv)
{
mu_error ("%spmult_mail: %s", PRIV_SEID_C (p), mu_strerror (ENOMEM));
free (kv[1]);
return SMTP_R_CONT;
}
x.argv[0] = (char *) mail;
for (i = 0; i < n; i++)
x.argv[i+1] = argv[i];
x.argv[i+1] = NULL;
x.ident = "envfrom";
x.srvfun = gacopyz_srv_envfrom;
status = pmult_runlist (p, rf_envfun, pmse_ctx, &x, kv);
free (kv[1]);
free (x.argv);
return status;
}
int
parse_email_addr (const char *arg, char **psender, char **addr, char **host)
{
size_t len;
const char *p = arg, *q;
if (*p == '<')
{
len = strlen (p);
if (p[len-1] != '>')
return 1;
p++;
*psender = malloc (len - 1);
if (*psender)
{
memcpy (*psender, p, len - 2);
(*psender)[len - 2] = 0;
}
}
else
*psender = strdup (arg);
if (!*psender)
return 1;
p = *psender;
q = strchr (p, '@');
if (q)
len = q - p;
else
len = strlen (p);
*addr = malloc (len + 1);
if (!*addr)
{
free (*psender);
return 1;
}
memcpy (*addr, p, len);
(*addr)[len] = 0;
if (q)
q++;
else
q = "localhost";
*host = strdup (q);
if (!*host)
{
free (*psender);
free (*addr);
return 1;
}
return 0;
}
static sfsistat_T
pmult_rcpt (pmse_ctx_P pmse_ctx, const char *rcpt, char **argv)
{
struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
char *sender, *addr, *host;
char *kv[9];
int i, n;
struct env_arg x;
sfsistat_T status;
char buf1[128], buf2[128];
if (mu_debug_check_level (p->debug, MU_DEBUG_TRACE1))
{
int i;
__PMU_DEBUG2 (p->debug, MU_DEBUG_TRACE1, "%sRCPT TO: %s",
PRIV_SEID_C (p), rcpt);
if (argv)
{
for (i = 0; argv[i]; i++)
__PMU_DEBUG1 (p->debug, MU_DEBUG_TRACE1, " %s", argv[i]);
}
PMU_DEBUG (p->debug, MU_DEBUG_TRACE1, "\n");
}
p->nrcpt++;
if (parse_email_addr (rcpt, &sender, &addr, &host))
{
PMU_DEBUG2 (p->debug, MU_DEBUG_ERROR, "%sbad recipient address %s",
PRIV_SEID_C (p), rcpt);
p->nbadrcpts++;
return SMTP_R_CONT; /* eh? */
}
/* Populate macros array */
snprintf (buf1, sizeof buf1, "%u", p->nrcpt);
snprintf (buf2, sizeof buf2, "%u", p->nbadrcpts);
kv[0] = "nrcpts";
kv[1] = buf1;
kv[2] = "nbadrcpts";
kv[3] = buf2;
kv[4] = "rcpt_host";
kv[5] = host;
kv[6] = "rcpt_addr";
kv[7] = addr;
kv[8] = NULL;
/* Count arguments and allocate env array */
if (argv)
{
for (n = 0; argv[n]; n++)
;
}
else
n = 0;
x.argv = calloc (n + 2, sizeof (x.argv[0]));
if (!x.argv)
{
mu_error ("%spmult_rcpt: %s", PRIV_SEID_C (p), mu_strerror (ENOMEM));
return SMTP_R_CONT;
}
x.argv[0] = (char *) rcpt;
for (i = 0; i < n; i++)
x.argv[i+1] = argv[i];
x.argv[i+1] = NULL;
x.ident = "envrcpt";
x.srvfun = gacopyz_srv_envrcpt;
status = pmult_runlist (p, rf_envfun, pmse_ctx, &x, kv);
free (x.argv);
free (sender);
free (addr);
free (host);
return status;
}
static sfsistat_T
rf_data (pmse_ctx_P pmse_ctx, gacopyz_srv_t gsrv, void *data)
{
struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
int rc = gacopyz_srv_data (gsrv);
return pmult_std_reply (p, pmse_ctx, gsrv, rc, "data", "");
}
static sfsistat_T
pmult_data (pmse_ctx_P pmse_ctx)
{
struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
PMU_DEBUG1 (p->debug, MU_DEBUG_TRACE1, "%sDATA\n", PRIV_SEID_C (p));
return pmult_runlist (p, rf_data, pmse_ctx, NULL, NULL);
}
static sfsistat_T
pmult_unknown (pmse_ctx_P pmse_ctx, const char *cmd)
{
struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
PMU_DEBUG2 (p->debug, MU_DEBUG_TRACE1, "%sUnknown command %s\n",
PRIV_SEID_C (p), cmd);
return SMTP_R_CONT;
}
static sfsistat_T
rf_abort (pmse_ctx_P pmse_ctx, gacopyz_srv_t gsrv, void *data)
{
gacopyz_srv_abort (gsrv);
return SMTP_R_CONT;
}
static sm_ret_T
pmult_abort (pmse_ctx_P pmse_ctx)
{
struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
if (p)
{
PMU_DEBUG1 (p->debug, MU_DEBUG_TRACE1, "%sABORT\n", PRIV_SEID_C (p));
pmult_runlist (p, rf_abort, pmse_ctx, NULL, NULL);
pmult_clear (p);
}
return SM_SUCCESS;
}
struct body_chunk
{
const char *ident;
int (*srvfun) (gacopyz_srv_t, unsigned char *, size_t);
size_t size;
char *buf;
};
static sfsistat_T
rf_body (pmse_ctx_P pmse_ctx, gacopyz_srv_t gsrv, void *data)
{
struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
struct body_chunk *bc = data;
int rc = bc->srvfun (gsrv, bc->buf, bc->size);
sfsistat_T status = pmult_std_reply (p, pmse_ctx, gsrv, rc, bc->ident, NULL);
/* Meta1 won't call EOM if _msg handler returns SMT_R_CONT. Why?
I don't know neither care. */
return status == SMTP_R_CONT ? SMTP_R_OK : status;
}
static int
collect_headers (struct pmult_priv_data *p, unsigned char *buf, size_t len,
size_t *poff)
{
int rc;
size_t off = 0;
if (p->state == pmult_msg_state_initial)
{
mu_memory_stream_create (&p->hdrstream, NULL, MU_STREAM_NO_CHECK);
p->state = pmult_msg_state_headers;
}
for (;;)
{
if (p->state == pmult_msg_state_headers)
{
for (; off < len; off++)
if (buf[off] == '\r')
{
off++;
p->state = pmult_msg_state_cr1;
break;
}
}
if (off == len)
break;
if (p->state == pmult_msg_state_cr1 && buf[off] == '\n')
{
p->state = pmult_msg_state_crlf1;
off++;
}
if (off == len)
break;
if (p->state == pmult_msg_state_crlf1)
{
if (buf[off] == '\r')
{
p->state = pmult_msg_state_cr2;
off++;
}
else
p->state = pmult_msg_state_headers;
}
if (off == len)
break;
if (p->state == pmult_msg_state_cr2 && buf[off] == '\n')
{
p->state = pmult_msg_state_body;
off++;
break;
}
}
rc = mu_stream_sequential_write (p->hdrstream, buf, off);
if (rc)
{
mu_error ("%smu_stream_sequential_write: %s",
PRIV_SEID_C (p), mu_strerror (rc));
return 1;
}
*poff = off;
return 0;
}
struct header
{
const char *name;
const char *value;
};
static sfsistat_T
rf_header (pmse_ctx_P pmse_ctx, gacopyz_srv_t gsrv, void *data)
{
struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
struct header *hp = data;
int rc = gacopyz_srv_header (gsrv, (char*)hp->name, (char*)hp->value);
pmult_std_reply (p, pmse_ctx, gsrv, rc, "header", NULL);
/* FIXME: do I need to analyze its return? */
return SMTP_R_OK;
}
static sfsistat_T
rf_eoh (pmse_ctx_P pmse_ctx, gacopyz_srv_t gsrv, void *data)
{
struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
int rc = gacopyz_srv_eoh (gsrv);
pmult_std_reply (p, pmse_ctx, gsrv, rc, "eoh", NULL);
/* FIXME: do I need to analyze its return? */
return SMTP_R_OK;
}
static int
process_headers (pmse_ctx_P pmse_ctx, struct pmult_priv_data *p)
{
mu_off_t size;
size_t count, i;
int rc;
mu_transport_t tbuf;
/* FIXME-MU: This could be largely simplified if mu_header_t had
a _set_stream method. */
rc = mu_stream_size (p->hdrstream, &size);
if (rc)
{
mu_error (_("%sCannot get the size of the header stream: %s"),
PRIV_SEID_C (p), mu_strerror (rc));
return 1;
}
mu_stream_get_transport (p->hdrstream, &tbuf);
rc = mu_header_create (&p->hdr, (char*) tbuf, size, NULL);
if (rc)
{
mu_error (_("%sCannot create header: %s"),
PRIV_SEID_C (p), mu_strerror (rc));
return 1;
}
/* FIXME-MU: mu_header_get_iterator would be in place here. */
mu_header_get_field_count (p->hdr, &count);
for (i = 1; i <= count; i++)
{
struct header h;
if (mu_header_sget_field_name (p->hdr, i, &h.name)
|| mu_header_sget_field_value (p->hdr, i, &h.value))
continue;
pmult_runlist0 (p, rf_header, pmse_ctx, &h, NULL);
}
pmult_runlist0 (p, rf_eoh, pmse_ctx, NULL, NULL);
return 0;
}
#define EOM_MARK "\r\n.\r\n"
#define EOM_MARK_LEN (sizeof (EOM_MARK) - 1)
static sfsistat_T
pmult_msg_handler (pmse_ctx_P pmse_ctx, unsigned char *buf, size_t len)
{
struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
struct body_chunk bc;
if (p)
{
if (mu_debug_check_level (p->debug, MU_DEBUG_TRACE7))
__PMU_DEBUG4 (p->debug, MU_DEBUG_TRACE1, "%sBODY %lu %.*s\n",
PRIV_SEID_C (p), (unsigned long) len, (int) len, buf);
else
PMU_DEBUG2 (p->debug, MU_DEBUG_TRACE1, "%sBODY %lu\n",
PRIV_SEID_C (p), (unsigned long) len);
}
else
return SMTP_R_OK;
if (p->state != pmult_msg_state_body)
{
size_t off;
int rc = collect_headers (p, buf, len, &off);
if (rc)
return SMTP_R_ACCEPT; /* Better safe than sorry. */
if (p->state != pmult_msg_state_body)
return SMTP_R_OK; /* See comment to rf_body */
if (process_headers (pmse_ctx, p))
return SMTP_R_ACCEPT;
len -= off;
buf += off;
}
/* FIXME: This is not enough, the marker can be split between two
successive calls. */
if (len >= EOM_MARK_LEN
&& memcmp (buf + len - EOM_MARK_LEN, EOM_MARK, EOM_MARK_LEN) == 0)
len -= 3;
bc.ident = "body";
bc.srvfun = gacopyz_srv_body;
bc.size = len;
bc.buf = buf;
return pmult_runlist0 (p, rf_body, pmse_ctx, &bc, NULL);
}
static sfsistat_T
pmult_msg (pmse_ctx_P pmse_ctx, unsigned char *buf, size_t len)
{
sfsistat_T rc;
struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
PRIV_LOCK (p);
rc = pmult_msg_handler (pmse_ctx, buf, len);
PRIV_UNLOCK (p);
return rc;
}
static sfsistat_T
pmult_eom (pmse_ctx_P pmse_ctx)
{
struct pmult_priv_data *p = sm_pmfi_get_ctx_se (pmse_ctx);
struct body_chunk bc;
sfsistat_T rc;
PMU_DEBUG1 (p->debug, MU_DEBUG_TRACE1, "%sEOM\n", PRIV_SEID_C (p));
bc.ident = "eom";
bc.srvfun = gacopyz_srv_eom;
bc.size = 0;
bc.buf = "";
rc = pmult_runlist (p, rf_body, pmse_ctx, &bc, NULL);
pmult_clear (p);
return rc;
}
static sm_ret_T
pmult_signal (pmg_ctx_P pmg_ctx, int sig)
{
mu_diag_output (MU_DIAG_INFO, _("Got signal %d"), sig);
return SM_SUCCESS;
}
static pmilter_T pmilter = {
"pmult",
LPMILTER_VERSION,
SM_SCAP_PM_ALL,
0,
0,
0,
pmult_negotiate,
pmult_connect,
pmult_helo,
pmult_mail,
pmult_rcpt,
pmult_data,
pmult_msg,
pmult_eom,
pmult_abort,
pmult_close,
pmult_unknown,
pmult_signal
};
void *
main_thread (void *p)
{
pmg_ctx_P pmg_ctx = p;
mu_diag_output (MU_DIAG_INFO, _("%s starting"), program_version);
SM_ASSERT (sm_pmfi_start (pmg_ctx, &pmilter));
mu_diag_output (MU_DIAG_INFO, _("%s terminated"), program_version);
exit (EX_OK);
}
int
wait_for_signal ()
{
sigset_t set;
int rc;
sigemptyset (&set);
sigaddset (&set, SIGHUP);
sigaddset (&set, SIGTERM);
sigaddset (&set, SIGSEGV);
sigaddset (&set, SIGABRT);
/* sigaddset (&set, SIGINT); */
rc = pthread_sigmask (SIG_BLOCK, &set, NULL);
if (rc)
{
mu_error (_("Failed to set up signals: %s"),
mu_strerror (errno));
return EX_SOFTWARE;
}
sigwait (&set, &rc);
return EX_OK;
}
int
main (int argc, char **argv)
{
int rc;
size_t count;
pmg_ctx_P pmg_ctx;
uint32_t major, minor, patchlevel;
pthread_t tid;
mf_init_nls ();
if (!program_invocation_short_name)
program_invocation_short_name = argv[0];
argp_program_version_hook = version;
/* Set default logging */
log_setup (!stderr_closed_p ());
pmult_cfg_init ();
mu_argp_init (program_version, package_bugreport);
rc = mu_app_init (&argp, capa, pmult_cfg_param, argc, argv, 0, NULL, NULL);
if (rc)
exit (EX_CONFIG);
log_setup (log_to_stderr);
if (!portspec)
{
mu_error (_("URL to listen on was not specified."));
exit (EX_CONFIG);
}
if (!client_list || mu_list_count (client_list, &count) || count == 0)
{
mu_error (_("No clients configured."));
exit (EX_CONFIG);
}
SM_ASSERT (sm_pmfi_init (&pmg_ctx));
SM_ASSERT (sm_pmfi_version (pmg_ctx, &major, &minor, &patchlevel));
if (major != LPMILTER_VERSION_MAJOR)
{
mu_error (_("Version mismatch: compile_time=%d, run_time=%d"),
LPMILTER_VERSION_MAJOR, major);
exit (EX_CONFIG);
}
SM_ASSERT (sm_pmfi_setconn (pmg_ctx, portspec));
SM_ASSERT (sm_pmfi_set_ctx_g (pmg_ctx, NULL));
if (pidfile)
{
rc = mu_daemon_create_pidfile (pidfile);
if (rc)
mu_error (_("Cannot create PID file `%s': %s"),
pidfile, mu_strerror (rc));
}
if (no_sig_handler)
main_thread (pmg_ctx);
else
{
rc = pthread_create (&tid, NULL, main_thread, pmg_ctx);
if (rc)
{
mu_error (_("Cannot create main thread: %s"),
mu_strerror (errno));
exit (EX_SOFTWARE);
}
return wait_for_signal ();
}
}
void
xalloc_die ()
{
mu_error ("not enough memory");
abort ();
}
/*
Local Variables:
c-file-style: "gnu"
End:
*/
/* EOF */