ossp-pkg/lmtp2nntp/lmtp2nntp_main.c
/*
** OSSP lmtp2nntp - Mail to News Gateway
** Copyright (c) 2001-2003 Ralf S. Engelschall <rse@engelschall.com>
** Copyright (c) 2001-2003 The OSSP Project <http://www.ossp.org/>
** Copyright (c) 2001-2003 Cable & Wireless Germany <http://www.cw.com/de/>
**
** This file is part of OSSP lmtp2nntp, an LMTP speaking local
** mailer which forwards mails as Usenet news articles via NNTP.
** It can be found at http://www.ossp.org/pkg/tool/lmtp2nntp/.
**
** 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
** 2.0 of the License, 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 file; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
** USA, or contact the OSSP project <ossp@ossp.org>.
**
** lmtp2nntp_main.c: LMTP to NNTP main procedure
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/utsname.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <signal.h>
#include <stdarg.h>
/* third party (included) */
#include "lmtp2nntp_argz.h"
#include "lmtp2nntp_shpat.h"
#include "lmtp2nntp_daemon.h"
/* third party (linked in) */
#include "ex.h"
#include "str.h"
#include "l2.h"
#include "sa.h"
#include "var.h"
#include "val.h"
#include "popt.h"
/* library version check (compile-time) */
#define L2_VERSION_HEX_REQ 0x001200
#define L2_VERSION_STR_REQ "0.1.0"
#define STR_VERSION_HEX_REQ 0x009206
#define STR_VERSION_STR_REQ "0.9.6"
#ifdef L2_VERSION_HEX
#if L2_VERSION_HEX < L2_VERSION_HEX_REQ
#error "require a newer version of OSSP L2"
#endif
#endif
#ifdef STR_VERSION_HEX
#if STR_VERSION_HEX < STR_VERSION_HEX_REQ
#error "require a newer version of OSSP Str"
#endif
#endif
/* own headers */
#include "lmtp2nntp_global.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#if defined(HAVE_DMALLOC_H) && defined(WITH_DMALLOC)
#include "dmalloc.h"
#endif
#include "lmtp2nntp_option.h"
#include "lmtp2nntp_config.h"
#include "lmtp2nntp_lmtp.h"
#include "lmtp2nntp_nntp.h"
#include "lmtp2nntp_msg.h"
#include "lmtp2nntp_common.h"
#include "sa.h"
#define _LMTP2NNTP_VERSION_C_AS_HEADER_
#include "lmtp2nntp_version.c"
#undef _LMTP2NNTP_VERSION_C_AS_HEADER_
#ifndef FALSE
#define FALSE (1 != 1)
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif
#ifndef NUL
#define NUL '\0'
#endif
#define STDSTRLEN 512
static lmtp_rc_t lmtp_cb_lhlo(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx);
static lmtp_rc_t lmtp_cb_mail(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx);
static lmtp_rc_t lmtp_cb_rcpt(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx);
static lmtp_rc_t lmtp_cb_data(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx);
static lmtp_rc_t lmtp_cb_noop(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx);
static lmtp_rc_t lmtp_cb_rset(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx);
static lmtp_rc_t lmtp_cb_quit(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx);
static int helo_rfc0821domain(char *msg, char **domain);
static int helo_rfc1035domain(char *msg, char **domain);
static void catchsignal(int sig, ...);
static void initsession(struct session *session);
static void resetsession(struct session *session);
int groupmatch(char *, size_t, char *);
void logbook(l2_channel_t *ch, l2_level_t level, const char *fmt, ...)
{
va_list ap;
l2_channel_t *ch2 = NULL;
if (strchr(fmt, '$') == NULL) {
if (l2_channel_downstream(ch, &ch2) != L2_OK)
return;
ch = ch2;
}
va_start(ap, fmt);
l2_channel_vlog(ch, level, fmt, ap);
va_end(ap);
return;
}
static var_syntax_t syntax_ctx = {
'\\', /* escape */
'$', /* varinit */
'{', /* startdelim */
'}', /* enddelim */
'[', /* startindex */
']', /* endindex */
'#', /* current_index */
"a-zA-Z0-9.-" /* namechars */
};
static var_rc_t ctx_lookup(
var_t *var,
void *_ctx,
const char *var_ptr, size_t var_len, int var_idx,
const char **val_ptr, size_t *val_len, size_t *val_size)
{
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
var_rc_t rc;
char *cp;
optionval_t *ov;
rc = VAR_ERR_UNDEFINED_VARIABLE;
if (strncasecmp(var_ptr, "option.", strlen("option.")) == 0) {
cp = str_dupex(var_ptr, var_len);
if (val_get(ctx->val, cp, &ov) == VAL_OK) {
if ((var_idx == 0) && (ov->ndata == 1) && (ov->type == OPT_SINGLE)) { /* request first/only single value */
*val_ptr = ov->data.s;
*val_len = strlen(ov->data.s);
*val_size = 0;
rc = VAR_OK;
}
else if ((var_idx == 0) && (ov->ndata == 1) && (ov->type == OPT_FLAG)) { /* request first/only single value */
*val_ptr = ov->data.f == TRUE ? "yes" : "no";
*val_len = strlen(ov->data.s);
*val_size = 0;
rc = VAR_OK;
}
else if ((var_idx < ov->ndata) && (ov->type == OPT_MULTI)) { /* request second+ from multi value */
*val_ptr = ov->data.m[var_idx];
*val_len = strlen(ov->data.m[var_idx]);
*val_size = 0;
rc = VAR_OK;
}
}
free(cp);
}
else if ( (strncasecmp(var_ptr, "msg.header.", strlen("msg.header.")) == 0)
&& (ctx->msg != NULL)
) {
headerdata_t *hdI;
int n;
cp = str_dupex(var_ptr + strlen("msg.header."), var_len - strlen("msg.header.") + 1);
n = strlen(cp);
cp[n]= ':';
cp[n + 1] = NUL;
for (hdI = ctx->msg->hdFirst; hdI != NULL; hdI = hdI->next) { /* for each header */
if (hdI->name == NULL || strlen(hdI->name) == 0)
continue;
if (strcasecmp(cp, hdI->name) == 0)
break;
}
if (hdI != NULL) {
if ((var_idx == 0) && (hdI->ndata == 1)) { /* request first/only single value */
*val_ptr = hdI->data.s;
*val_len = strlen(hdI->data.s);
*val_size = 0;
rc = VAR_OK;
}
else if (var_idx < hdI->ndata) { /* request from multi value */
*val_ptr = hdI->data.m[var_idx];
*val_len = strlen(hdI->data.m[var_idx]);
*val_size = 0;
rc = VAR_OK;
}
}
free(cp);
}
return rc;
}
static void lmtp_gfs_ns(struct ns *);
static void lmtp_gfs_lhlo(lmtp2nntp_t *);
static void lmtp_gfs_rset(lmtp2nntp_t *);
static void lmtp_gfs_quit(lmtp2nntp_t *);
static void sa2errno(sa_rc_t rv)
{
switch (rv) {
case SA_OK: errno = 0; break;
case SA_ERR_EOF: errno = 0; break;
case SA_ERR_SYS: break;
case SA_ERR_ARG: errno = EINVAL; break;
case SA_ERR_USE: errno = EINVAL; break;
case SA_ERR_MTC: errno = ENOENT; break;
case SA_ERR_MEM: errno = ENOMEM; break;
case SA_ERR_IMP: errno = ENOSYS; break;
case SA_ERR_TMT: errno = EIO; break;
case SA_ERR_NET: errno = EIO; break;
case SA_ERR_FMT: errno = EIO; break;
case SA_ERR_INT: errno = EIO; break;
default: errno = EIO;
}
}
static ssize_t hook_lmtp_read(void *_ctx, void *buf, size_t nbytes)
{
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
ssize_t rc;
size_t n;
sa_rc_t rv;
if (ctx->saIO != NULL) {
if ((rv = sa_read(ctx->saIO, buf, nbytes, &n)) != SA_OK) {
if (rv == SA_ERR_SYS)
logbook(ctx->l2, L2_LEVEL_DEBUG, "hook_lmtp_read() sa_read failed with \"%s\" (%d) %s", sa_error(rv), errno, strerror(errno));
else
logbook(ctx->l2, L2_LEVEL_DEBUG, "hook_lmtp_read() sa_read failed with \"%s\"", sa_error(rv));
if (rv == SA_ERR_EOF)
rc = 0;
else
rc = -1;
sa2errno(rv);
}
else
rc = (ssize_t)n;
}
else {
rc = read(ctx->fdIOi, buf, nbytes);
if (rc == -1)
logbook(ctx->l2, L2_LEVEL_DEBUG, "hook_lmtp_read() read failed with (%d) %s", errno, strerror(errno));
}
if (rc == -1)
logbook(ctx->l2, L2_LEVEL_TRACE, "LMTP read error: %m");
else
logbook(ctx->l2, L2_LEVEL_TRACE, "LMTP %5d << \"%{text}D\"", rc, buf, rc);
logbook(ctx->l2, L2_LEVEL_DEBUG, "hook_lmtp_read() return, rc=%d", rc);
return rc;
}
static ssize_t hook_lmtp_write(void *_ctx, const void *buf, size_t nbytes)
{
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
ssize_t rc;
size_t n;
sa_rc_t rv;
logbook(ctx->l2, L2_LEVEL_TRACE, "LMTP %5d >> \"%{text}D\"", nbytes, buf, nbytes);
if (ctx->saIO != NULL) {
if ((rv = sa_write(ctx->saIO, buf, nbytes, &n)) != SA_OK) {
if (rv == SA_ERR_SYS)
logbook(ctx->l2, L2_LEVEL_DEBUG, "hook_lmtp_write() sa_write failed with \"%s\" (%d) %s", sa_error(rv), errno, strerror(errno));
else
logbook(ctx->l2, L2_LEVEL_DEBUG, "hook_lmtp_write() sa_write failed with \"%s\"", sa_error(rv));
rc = -1;
sa2errno(rv);
}
else
rc = (ssize_t)n;
}
else {
rc = write(ctx->fdIOo, buf, nbytes);
if (rc == -1)
logbook(ctx->l2, L2_LEVEL_DEBUG, "hook_lmtp_write() write failed with (%d) %s", errno, strerror(errno));
}
if (rc == -1)
logbook(ctx->l2, L2_LEVEL_TRACE, "LMTP write error");
return rc;
}
static ssize_t hook_nntp_read(void *_ctx, void *buf, size_t nbytes)
{
struct ns *ctx = (struct ns *)_ctx;
ssize_t rc;
size_t n;
sa_rc_t rv;
if ((rv = sa_read(ctx->sa, buf, nbytes, &n)) != SA_OK) {
if (rv == SA_ERR_SYS)
logbook(ctx->l2, L2_LEVEL_DEBUG, "hook_lmtp_read() sa_read failed with \"%s\" (%d) %s", sa_error(rv), errno, strerror(errno));
else
logbook(ctx->l2, L2_LEVEL_DEBUG, "hook_lmtp_read() sa_read failed with \"%s\"", sa_error(rv));
if (rv == SA_ERR_EOF)
rc = 0;
else
rc = -1;
sa2errno(rv);
}
else
rc = (ssize_t)n;
if (rc == -1)
logbook(ctx->l2, L2_LEVEL_TRACE, "NNTP read error: %m");
else
logbook(ctx->l2, L2_LEVEL_TRACE, "NNTP %5d << \"%{text}D\"", rc, buf, rc);
return rc;
}
static ssize_t hook_nntp_write(void *_ctx, const void *buf, size_t nbytes)
{
struct ns *ctx = (struct ns *)_ctx;
ssize_t rc;
size_t n;
sa_rc_t rv;
logbook(ctx->l2, L2_LEVEL_TRACE, "NNTP %5d >> \"%{text}D\"", nbytes, buf, nbytes);
if ((rv = sa_write(ctx->sa, buf, nbytes, &n)) != SA_OK) {
if (rv == SA_ERR_SYS)
logbook(ctx->l2, L2_LEVEL_DEBUG, "hook_nntp_write() sa_write failed with \"%s\" (%d) %s", sa_error(rv), errno, strerror(errno));
else
logbook(ctx->l2, L2_LEVEL_DEBUG, "hook_nntp_write() sa_write failed with \"%s\"", sa_error(rv));
rc = -1;
sa2errno(rv);
}
else
rc = (ssize_t)n;
if (rc == -1)
logbook(ctx->l2, L2_LEVEL_TRACE, "NNTP write error");
return rc;
}
static void catchsignal(int sig, ...)
{
va_list ap;
static lmtp2nntp_t *ctx = NULL;
pid_t pid;
if(sig == 0) {
va_start(ap, sig);
if ((ctx = va_arg(ap, lmtp2nntp_t *)) == NULL)
exit(ERR_EXECUTION);
logbook(ctx->l2, L2_LEVEL_TRACE, "catching and logging signals now");
va_end(ap);
return;
}
if (ctx != NULL) {
switch (sig) {
case SIGCHLD:
logbook(ctx->l2, L2_LEVEL_NOTICE, "caught signal %d - wait for child", sig);
pid = wait(NULL);
ctx->active_childs--;
logbook(ctx->l2, L2_LEVEL_NOTICE, "caught signal %d - child [%ld] terminated", sig, (long)pid);
signal(sig, (void(*)())catchsignal);
return;
case SIGUSR1:
logbook(ctx->l2, L2_LEVEL_NOTICE, "caught signal %d - flush logging stream", sig);
l2_channel_flush(ctx->l2);
signal(sig, (void(*)())catchsignal);
return;
case SIGHUP:
case SIGINT:
case SIGQUIT:
logbook(ctx->l2, L2_LEVEL_NOTICE, "caught signal %d - exit - no more logging", sig);
break;
default:
logbook(ctx->l2, L2_LEVEL_PANIC, "CAUGHT SIGNAL %d - EXIT - NO MORE LOGGING", sig);
}
l2_channel_destroy(ctx->l2);
l2_env_destroy(ctx->l2_env);
}
exit(ERR_EXECUTION);
}
int main(int argc, char **argv)
{
int rc;
lmtp_t *lmtp = NULL;
lmtp_io_t lmtp_io;
lmtp2nntp_t *ctx = NULL;
int bOk;
int i; /* general purpose scratch int, index ... */
pid_t pid;
FILE *fd;
lmtp2nntp_option_t *o;
/* drop effective uid/gid privileges */
seteuid(getuid());
setegid(getgid());
/* use unbuffered stdio */
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
/* library version check (run-time) */
if (l2_version.v_hex < L2_VERSION_HEX_REQ) {
fprintf(stderr, "require OSSP L2 >= %s, found %s\n", L2_VERSION_STR_REQ, L2_VERSION_STR);
CU(ERR_EXECUTION);
}
if (str_version.v_hex < STR_VERSION_HEX_REQ) {
fprintf(stderr, "require OSSP Str >= %s, found %s\n", STR_VERSION_STR_REQ, STR_VERSION_STR);
CU(ERR_EXECUTION);
}
/* create application context. fields are initialized to values that allow
* detection of dynamic allocated resources which must be freed up for
* graceful cleanup. These values are not necessarily useful application
* defaults. Those defaults must be configured in lmtp2nntp_option.c
* through option_register() calls */
if ((ctx = (lmtp2nntp_t *)malloc(sizeof(lmtp2nntp_t))) == NULL)
CU(ERR_EXECUTION);
ctx->ctx.vp = ctx;
ctx->prival = NULL;
ctx->val = NULL;
ctx->progname = NULL;
ctx->option_groupmode = GROUPMODE_UNDEF;
ctx->option_operationmode = OPERATIONMODE_UNDEF;
ctx->option_operationmodefakestatus = NULL;
ctx->option_operationmodefakedsn = NULL; /* is joined to fakestatus in a single malloc(3) */
ctx->option_maxmessagesize = 0;
ctx->option_firstheaderrule = NULL;
ctx->option_timeout_lmtp_accept = 0;
ctx->option_timeout_lmtp_read = 0;
ctx->option_timeout_lmtp_write = 0;
ctx->option_timeout_nntp_connect = 0;
ctx->option_timeout_nntp_read = 0;
ctx->option_timeout_nntp_write = 0;
ctx->option_nodename = NULL;
ctx->option_mailfrom = NULL;
ctx->option_restrictheader = NULL;
ctx->option_pidfile = NULL;
ctx->option_killflag = FALSE;
ctx->option_uid = getuid();
ctx->option_daemon = FALSE;
ctx->nacl = 0;
ctx->pacl = NULL;
ctx->option_childsmax = 0;
ctx->active_childs = 0;
ctx->l2_env = NULL;
ctx->l2 = NULL;
ctx->saaServerbind = NULL;
ctx->saServerbind = NULL;
ctx->saaClientbind = NULL;
ctx->saClientbind = NULL;
ctx->saaIO = NULL;
ctx->saIO = NULL;
ctx->fdIOi = -1;
ctx->fdIOo = -1;
ctx->nns = 0;
ctx->pns = NULL;
ctx->azGroupargs = NULL;
ctx->asGroupargs = 0;
initsession(&ctx->session);
ctx->msg = NULL;
ctx->config_varregex = NULL;
ctx->config_varctx = NULL;
ctx->msgcount = 0;
/* private application context */
if (val_create(&ctx->prival) != VAL_OK)
CU(ERR_EXECUTION);
/* create printable variables context and mount it into the private application context */
if (val_create(&ctx->val) != VAL_OK)
CU(ERR_EXECUTION);
if (val_reg(ctx->prival, "printable", VAL_TYPE_VAL, "printable variables", NULL) != VAL_OK)
CU(ERR_EXECUTION);
if (val_set(ctx->prival, "printable", ctx->val) != VAL_OK)
CU(ERR_EXECUTION);
/* feed lib_val */
if (val_reg(ctx->prival, "msgcount", VAL_TYPE_INT, "number of messages processed so far", (void *)&ctx->msgcount) != VAL_OK)
CU(ERR_EXECUTION);
if (val_reg(ctx->prival, "nodename", VAL_TYPE_PTR, "nodename configured or uname(3)", (void *)&ctx->option_nodename) != VAL_OK)
CU(ERR_EXECUTION);
/* set progname */
ctx->progname = strdup(argv[0]);
/* establish variable expansion context */
{
char *cp;
if ((rc = var_create(&ctx->config_varctx)) != VAR_OK) {
fprintf(stderr, "%s:Error: create varctx context failed with %s (%d)", ctx->progname, var_strerror(ctx->config_varctx, rc, &cp) == VAR_OK ? cp : "Unknown Error", rc);
CU(ERR_EXECUTION);
}
if ((rc = var_config(ctx->config_varctx, VAR_CONFIG_SYNTAX, &syntax_ctx)) != VAR_OK) {
fprintf(stderr, "%s:Error: config varctx context failed with %s (%d)", ctx->progname, var_strerror(ctx->config_varctx, rc, &cp) == VAR_OK ? cp : "Unknown Error", rc);
CU(ERR_EXECUTION);
}
if ((rc = var_config(ctx->config_varctx, VAR_CONFIG_CB_VALUE, ctx_lookup, ctx)) != VAR_OK) {
fprintf(stderr, "%s:Error: config varctx callback failed with %s (%d)", ctx->progname, var_strerror(ctx->config_varctx, rc, &cp) == VAR_OK ? cp : "Unknown Error", rc);
CU(ERR_EXECUTION);
}
}
/* read in the arguments */
{
lmtp2nntp_option_rc_t rvo;
lmtp2nntp_config_rc_t rvc;
ex_t ex;
if (option_create(&o, ctx->val) != OPTION_OK)
CU(ERR_EXECUTION);
rvo = option_parse(o, argc, argv);
rvc = CONFIG_OK;
try {
rvc = config_context(ctx);
}
catch(ex) {
rvc = CONFIG_ERR_TRY;
}
if (rvc == CONFIG_OK_DRY)
CU(0);
if (rvo != OPTION_OK || rvc != CONFIG_OK)
CU(ERR_EXECUTION);
}
if (getuid() != ctx->option_uid) {
if (setuid(ctx->option_uid) == -1) {
fprintf(stderr, "%s:Error: Setting UID to %d failed: %s\n",
ctx->progname, (int)ctx->option_uid, strerror(errno));
CU(ERR_EXECUTION);
}
}
if ((ctx->option_pidfile != NULL) && ctx->option_killflag) {
if ((fd = fopen(ctx->option_pidfile, "r")) == NULL)
logbook(ctx->l2, L2_LEVEL_ERROR, "cannot open pidfile \"%s\" for reading %m", ctx->option_pidfile);
else {
if (fscanf(fd, "%d\n", (int *)&pid) != 1) {
fclose(fd);
logbook(ctx->l2, L2_LEVEL_ERROR, "cannot extract pid from pidfile \"%s\"", ctx->option_pidfile);
}
else {
fclose(fd);
logbook(ctx->l2, L2_LEVEL_TRACE, "going to kill pid[%d]", pid);
if (kill(pid, SIGHUP) == -1)
logbook(ctx->l2, L2_LEVEL_ERROR, "killing pid[%d] failed %m", pid);
if (unlink(ctx->option_pidfile) == -1)
logbook(ctx->l2, L2_LEVEL_ERROR, "unlinking pidfile \"%s\" failed %m", ctx->option_pidfile);
}
}
CU(0);
}
catchsignal(0, ctx);
signal(SIGCHLD, (void(*)())catchsignal);
signal(SIGHUP, (void(*)())catchsignal);
signal(SIGINT, (void(*)())catchsignal);
signal(SIGQUIT, (void(*)())catchsignal);
signal(SIGILL, (void(*)())catchsignal);
signal(SIGBUS, (void(*)())catchsignal);
signal(SIGSEGV, (void(*)())catchsignal);
signal(SIGSYS, (void(*)())catchsignal);
signal(SIGTERM, (void(*)())catchsignal);
signal(SIGUSR1, (void(*)())catchsignal);
signal(SIGUSR2, SIG_IGN );
/* loop for LMTP protocol with support for alternate io through daemon */
if (ctx->saServerbind == NULL) {
/* initialize LMTP context */
ctx->fdIOi = STDIN_FILENO;
ctx->fdIOo = STDOUT_FILENO;
lmtp_io.ctx = ctx;
lmtp_io.read = hook_lmtp_read;
lmtp_io.write = hook_lmtp_write;
if ((lmtp = lmtp_create(&lmtp_io)) == NULL) {
logbook(ctx->l2, L2_LEVEL_ERROR, "Unable to initialize LMTP library\n");
CU(ERR_EXECUTION);
}
/* RFC0821, 4.5.1. MINIMUM IMPLEMENTATION
* In order to make SMTP workable, the following minimum implementation
* is required for all receivers: [...]
* RFC0821, 4.1.2. COMMAND SYNTAX
*
* Verb Parameter
* ----+-------------------------------
* HELO <SP> <domain> <CRLF>
* MAIL <SP> FROM:<reverse-path> <CRLF>
* RCPT <SP> TO:<forward-path> <CRLF>
* DATA <CRLF>
* RSET <CRLF>
* NOOP <CRLF>
* QUIT <CRLF>
*/
lmtp_register(lmtp, "LHLO", lmtp_cb_lhlo, ctx, NULL, NULL);
lmtp_register(lmtp, "MAIL", lmtp_cb_mail, ctx, NULL, NULL);
lmtp_register(lmtp, "RCPT", lmtp_cb_rcpt, ctx, NULL, NULL);
lmtp_register(lmtp, "DATA", lmtp_cb_data, ctx, NULL, NULL);
lmtp_register(lmtp, "RSET", lmtp_cb_rset, ctx, NULL, NULL);
lmtp_register(lmtp, "NOOP", lmtp_cb_noop, ctx, NULL, NULL);
lmtp_register(lmtp, "QUIT", lmtp_cb_quit, ctx, NULL, NULL);
lmtp_loop(lmtp);
lmtp_gfs_quit(ctx);
lmtp_gfs_lhlo(ctx);
lmtp_destroy(lmtp);
} else {
pid = getpid();
if (ctx->option_daemon) {
daemonize();
logbook(ctx->l2, L2_LEVEL_NOTICE, "daemonized, previous pid[%d]", pid);
}
if (ctx->option_pidfile != NULL) {
if ((fd = fopen(ctx->option_pidfile, "w+")) == NULL)
logbook(ctx->l2, L2_LEVEL_ERROR, "cannot open pidfile \"%s\" for writing %m", ctx->option_pidfile);
else {
fprintf(fd, "%d\n", (int)getpid());
fclose(fd);
}
}
sa_timeout(ctx->saServerbind, SA_TIMEOUT_ALL, 0, 0);
sa_timeout(ctx->saServerbind, SA_TIMEOUT_ACCEPT, ctx->option_timeout_lmtp_accept, 0);
sa_timeout(ctx->saServerbind, SA_TIMEOUT_READ, ctx->option_timeout_lmtp_read, 0);
sa_timeout(ctx->saServerbind, SA_TIMEOUT_WRITE, ctx->option_timeout_lmtp_write, 0);
while (1) {
while (ctx->active_childs >= ctx->option_childsmax) {
logbook(ctx->l2, L2_LEVEL_ERROR, "maximum number of childs (%d) reached - waiting (1s)", ctx->option_childsmax);
sleep(1);
}
if ((rc = sa_accept(ctx->saServerbind, &ctx->saaIO, &ctx->saIO)) != SA_OK) {
if (rc == SA_ERR_SYS)
logbook(ctx->l2, L2_LEVEL_ERROR, "accept failed: %s: (%d) %s", sa_error(rc), errno, strerror(errno));
else
logbook(ctx->l2, L2_LEVEL_ERROR, "accept failed: %s", sa_error(rc));
sleep(10);
continue;
}
/* Access Control List */
bOk = FALSE;
/* check positive matches */
for (i = 0; i < ctx->nacl; i++) {
char *cpA1;
char *cpA2;
if (ctx->pacl[i].not)
continue;
sa_addr_a2u(ctx->pacl[i].saa, &cpA1);
sa_addr_a2u(ctx->saaIO, &cpA2);
if (sa_addr_match(ctx->saaIO, ctx->pacl[i].saa, ctx->pacl[i].prefixlen) == SA_OK) {
logbook(ctx->l2, L2_LEVEL_TRACE, "positive/inclusive ACL \"%s\" (%s/%d) matches %s: YES (stop comparison)", ctx->pacl[i].acl, cpA1, ctx->pacl[i].prefixlen, cpA2);
bOk = TRUE;
break;
}
else
logbook(ctx->l2, L2_LEVEL_TRACE, "positive/inclusive ACL \"%s\" (%s/%d) matches %s: NO", ctx->pacl[i].acl, cpA1, ctx->pacl[i].prefixlen, cpA2);
free(cpA1);
free(cpA2);
}
/* check negative matches */
for (i = 0; i < ctx->nacl; i++) {
char *cpA1;
char *cpA2;
if (!ctx->pacl[i].not)
continue;
sa_addr_a2u(ctx->pacl[i].saa, &cpA1);
sa_addr_a2u(ctx->saaIO, &cpA2);
if (sa_addr_match(ctx->saaIO, ctx->pacl[i].saa, ctx->pacl[i].prefixlen) == SA_OK) {
logbook(ctx->l2, L2_LEVEL_TRACE, "negative/exclusive ACL \"%s\" (not %s/%d) matches %s: YES (stop comparison)", ctx->pacl[i].acl, cpA1, ctx->pacl[i].prefixlen, cpA2);
bOk = FALSE;
break;
}
else {
logbook(ctx->l2, L2_LEVEL_TRACE, "negative/exclusive ACL \"%s\" (not %s/%d) matches %s: NO", ctx->pacl[i].acl, cpA1, ctx->pacl[i].prefixlen, cpA2);
}
}
if (bOk) {
char *cpA;
sa_addr_a2u(ctx->saaIO, &cpA);
logbook(ctx->l2, L2_LEVEL_TRACE, "connection from %s accepted due to ACL", cpA);
free(cpA);
}
else {
char *cpA;
sa_addr_a2u(ctx->saaIO, &cpA);
logbook(ctx->l2, L2_LEVEL_ERROR, "connection from %s refused due to ACL", cpA);
free(cpA);
sa_destroy(ctx->saIO);
sa_addr_destroy(ctx->saaIO);
continue;
}
/* logging buffer must be empty before fork otherwise content is
* duplicated and written twice on next flush */
l2_channel_flush(ctx->l2);
pid = fork();
if (pid == -1) {
logbook(ctx->l2, L2_LEVEL_ERROR, "daemon cannot spawn child %m");
continue;
}
if (pid != 0) {
logbook(ctx->l2, L2_LEVEL_INFO, "daemon forked process, new child pid[%d]", pid);
ctx->active_childs++;
sa_destroy(ctx->saIO);
sa_addr_destroy(ctx->saaIO);
continue;
}
logbook(ctx->l2, L2_LEVEL_NOTICE, "startup new child process, parent pid[%d]", getppid());
/* child must close listening socket */
sa_destroy(ctx->saServerbind);
ctx->saServerbind = NULL; /* prevent cleanup from free'ing this again */
/* initialize LMTP context */
lmtp_io.ctx = ctx;
lmtp_io.read = hook_lmtp_read;
lmtp_io.write = hook_lmtp_write;
if ((lmtp = lmtp_create(&lmtp_io)) == NULL) {
logbook(ctx->l2, L2_LEVEL_ERROR, "Unable to initialize LMTP library\n");
CU(ERR_EXECUTION);
}
/* RFC0821, 4.5.1. MINIMUM IMPLEMENTATION
* In order to make SMTP workable, the following minimum implementation
* is required for all receivers: [...]
* RFC0821, 4.1.2. COMMAND SYNTAX
*
* Verb Parameter
* ----+-------------------------------
* HELO <SP> <domain> <CRLF>
* MAIL <SP> FROM:<reverse-path> <CRLF>
* RCPT <SP> TO:<forward-path> <CRLF>
* DATA <CRLF>
* RSET <CRLF>
* NOOP <CRLF>
* QUIT <CRLF>
*/
lmtp_register(lmtp, "LHLO", lmtp_cb_lhlo, ctx, NULL, NULL);
lmtp_register(lmtp, "MAIL", lmtp_cb_mail, ctx, NULL, NULL);
lmtp_register(lmtp, "RCPT", lmtp_cb_rcpt, ctx, NULL, NULL);
lmtp_register(lmtp, "DATA", lmtp_cb_data, ctx, NULL, NULL);
lmtp_register(lmtp, "RSET", lmtp_cb_rset, ctx, NULL, NULL);
lmtp_register(lmtp, "NOOP", lmtp_cb_noop, ctx, NULL, NULL);
lmtp_register(lmtp, "QUIT", lmtp_cb_quit, ctx, NULL, NULL);
lmtp_loop(lmtp);
lmtp_gfs_quit(ctx);
lmtp_gfs_lhlo(ctx);
lmtp_destroy(lmtp);
CU(0);
}
}
CU(0);
/* graceful shutdown */
CUS:
signal(SIGCHLD, SIG_DFL);
signal(SIGHUP, SIG_DFL);
signal(SIGINT, SIG_DFL);
signal(SIGQUIT, SIG_DFL);
signal(SIGILL, SIG_DFL);
signal(SIGBUS, SIG_DFL);
signal(SIGSEGV, SIG_DFL);
signal(SIGSYS, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGUSR1, SIG_DFL);
signal(SIGUSR2, SIG_DFL);
logbook(ctx->l2, L2_LEVEL_NOTICE, "graceful shutdown shortly before exit - no more logging");
if (ctx->l2 != NULL)
l2_channel_destroy(ctx->l2);
if (ctx->l2_env != NULL)
l2_env_destroy(ctx->l2_env);
if (ctx->saServerbind)
sa_destroy(ctx->saServerbind);
if (ctx->saaServerbind)
sa_addr_destroy(ctx->saaServerbind);
if (ctx->option_restrictheader != NULL)
free(ctx->option_restrictheader);
if (ctx->option_pidfile != NULL)
free(ctx->option_pidfile);
if (ctx->pns != NULL)
free(ctx->pns);
if (ctx->pacl != NULL) {
for (i = 0; i < ctx->nacl; i++)
if (ctx->pacl[i].saa != NULL)
sa_addr_destroy(ctx->pacl[i].saa);
free(ctx->pacl);
}
if (ctx->option_nodename != NULL)
free(ctx->option_nodename);
if (ctx->prival != NULL)
val_destroy(ctx->prival);
if (ctx->val != NULL)
val_destroy(ctx->val);
if (ctx->progname != NULL)
free(ctx->progname);
if (ctx->azGroupargs != NULL)
free(ctx->azGroupargs);
if (ctx->config_varregex != NULL)
var_destroy(ctx->config_varregex);
if (ctx->config_varctx != NULL)
var_destroy(ctx->config_varctx);
if (ctx->option_operationmodefakestatus != NULL)
free(ctx->option_operationmodefakestatus); /* includes dsn */
if (ctx != NULL)
free(ctx);
str_parse(NULL, NULL);
if (o != NULL)
(void)option_destroy(o);
return rc;
}
static void resetsession(struct session *session)
{
if (session->lhlo_domain != NULL)
free(session->lhlo_domain);
initsession(session);
return;
}
static void initsession(struct session *session)
{
session->lhlo_seen = FALSE;
session->lhlo_domain = NULL;
return;
}
static lmtp_rc_t lmtp_cb_lhlo(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx)
{
/*
* RFC0821 [excerpt] 4.1. SMTP COMMANDS
* 4.1.1. COMMAND SEMANTICS, HELO
* This command and an OK reply to it confirm that both the sender-SMTP
* and the receiver-SMTP are in the initial state, that is, there is no
* transaction in progress and all state tables and buffers are cleared.
*
* The first command in a session must be the HELO command. The HELO
* command may be used later in a session as well. If the HELO command
* argument is not acceptable a 501 failure reply must be returned and
* the receiver-SMTP must stay in the same state.
*
* If the transaction beginning command argument is not acceptable a 501
* failure reply must be returned and the receiver-SMTP must stay in the
* same state. If the commands in a transaction are out of order a 503
* failure reply must be returned and the receiver-SMTP must stay in the
* same state.
*
* HELO <SP> <domain> <CRLF>
*/
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
nntp_rc_t rc;
lmtp_res_t res;
char str[STDSTRLEN];
int bOk;
int i;
int is;
nntp_io_t nntp_io;
logbook(ctx->l2, L2_LEVEL_INFO, "LMTP service executing LHLO command < %s", req->msg);
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 503 Bad sequence of commands
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.0.0 Other undefined Status
*/
logbook(ctx->l2, L2_LEVEL_TRACE, "checking for duplicate LHLO");
if (ctx->session.lhlo_seen) {
res.statuscode = "503";
res.dsncode = "5.0.0";
res.statusmsg = "Duplicate LHLO.";
CU(LMTP_OK);
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 501 Syntax error in parameters or arguments
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.0.0 Other undefined Status
*/
logbook(ctx->l2, L2_LEVEL_TRACE, "checking domain to match either RFC0821 or RFC1035 syntax");
if (! ( helo_rfc0821domain(req->msg, &ctx->session.lhlo_domain) > 0
|| helo_rfc1035domain(req->msg, &ctx->session.lhlo_domain) > 0)) {
res.statuscode = "501";
res.dsncode = "5.0.0";
res.statusmsg = "Please identify yourself. Domain must match RFC0821/RFC1035.";
CU(LMTP_OK);
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 451 Requested action aborted: local error in processing
* RFC1893 2. Status Codes 4.X.X Persistent Transient Failure
* RFC1893 3.5 Network and Routing Status X.3.5 System incorrectly configured
*/
if (ctx->option_operationmode != OPERATIONMODE_FAKE) {
logbook(ctx->l2, L2_LEVEL_TRACE, "check if at least one NNTP service was successfully configured");
if (ctx->nns == 0) {
res.statuscode = "451";
res.dsncode = "4.3.5";
res.statusmsg = "No valid NNTP services configured.";
CU(LMTP_OK);
}
}
logbook(ctx->l2, L2_LEVEL_TRACE, "try to establish a session to any configured NNTP services");
if (ctx->option_operationmode == OPERATIONMODE_FAKE)
logbook(ctx->l2, L2_LEVEL_NOTICE, "NNTP running in fake mode, network connections will be executed but result is ignored");
i = 0;
is = 0;
while (i < ctx->nns) {
logbook(ctx->l2, L2_LEVEL_DEBUG, "trying ns[%d]", i);
bOk = TRUE;
logbook(ctx->l2, L2_LEVEL_TRACE, "try destination#%d ${option.destination[%d]}", i, i);
ctx->pns[i].l2 = ctx->l2;
if (bOk && (ctx->saaClientbind != NULL)) {
logbook(ctx->l2, L2_LEVEL_DEBUG, "bind local socket to ${option.client}");
if (sa_bind(ctx->pns[i].sa, ctx->saaClientbind) != SA_OK) {
bOk = FALSE;
logbook(ctx->l2, L2_LEVEL_ERROR, "binding NNTP client to local address ${option.client} failed, %m");
}
}
sa_timeout(ctx->pns[i].sa, SA_TIMEOUT_ALL, 0, 0);
sa_timeout(ctx->pns[i].sa, SA_TIMEOUT_CONNECT, ctx->option_timeout_nntp_connect, 0);
sa_timeout(ctx->pns[i].sa, SA_TIMEOUT_READ, ctx->option_timeout_nntp_read, 0);
sa_timeout(ctx->pns[i].sa, SA_TIMEOUT_WRITE, ctx->option_timeout_nntp_read, 0);
if (bOk) {
logbook(ctx->l2, L2_LEVEL_DEBUG, "connect");
if (sa_connect(ctx->pns[i].sa, ctx->pns[i].saa) != SA_OK) {
bOk = FALSE;
logbook(ctx->l2, L2_LEVEL_WARNING, "connect to destination#%d ${option.destination[%d]} failed, %m", i, i);
}
}
if (bOk) {
logbook(ctx->l2, L2_LEVEL_DEBUG, "nntp_create");
nntp_io.ctx = &ctx->pns[i];
nntp_io.read = hook_nntp_read;
nntp_io.write = hook_nntp_write;
if ((ctx->pns[i].nntp = nntp_create(&nntp_io)) == NULL) {
bOk = FALSE;
logbook(ctx->l2, L2_LEVEL_ERROR, "creation of NNTP context failed");
}
}
if (bOk) {
logbook(ctx->l2, L2_LEVEL_DEBUG, "nntp_init");
if ((rc = nntp_init(ctx->pns[i].nntp)) != NNTP_OK) {
bOk = FALSE;
logbook(ctx->l2, L2_LEVEL_ERROR, "initialization of NNTP context failed, (%d) %s", rc, nntp_error(rc));
}
}
if (bOk) {
logbook(ctx->l2, L2_LEVEL_INFO, "NNTP session to destination#%d ${option.destination[%d]} successfully established", i, i);
is++;
}
else {
logbook(ctx->l2, L2_LEVEL_WARNING, "NNTP session establishment to destination#%d ${option.destination[%d]} failed", i, i);
lmtp_gfs_ns(&ctx->pns[i]);
}
i++;
}
logbook(ctx->l2, L2_LEVEL_INFO, "NNTP network connections tried %d, successful %d", i, is);
if (ctx->option_operationmode == OPERATIONMODE_FAKE)
logbook(ctx->l2, L2_LEVEL_NOTICE, "NNTP running in fake mode, ignoring status of real network connections");
else
{
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 421 <domain> Service not available
* RFC1893 2. Status Codes 4.X.X Persistent Transient Failure
* RFC1893 3.5 Network and Routing Status X.4.1 No answer from host
*/
logbook(ctx->l2, L2_LEVEL_DEBUG, "check if at least one NNTP session successfully established");
if (is == 0) {
logbook(ctx->l2, L2_LEVEL_ERROR, "no NNTP session established");
res.statuscode = "421";
res.dsncode = "4.4.1";
res.statusmsg = "No NNTP session established.";
CU(LMTP_OK);
}
}
ctx->session.lhlo_seen = TRUE;
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 250 Requested mail action okay, completed
*/
str_format(str, sizeof(str),
"%s Hello %s, pleased to meet you.\n" /* RFC2821 4.1.1.1 */
"ENHANCEDSTATUSCODES\n" /* RFC2034 */
"DSN\n" /* RFC1894 */
"PIPELINING\n" /* RFC1854 */
"8BITMIME", /* RFC1652 */
ctx->option_nodename,
ctx->session.lhlo_domain);
res.statuscode = "250";
res.dsncode = NULL; /* DSN not used for greeting */
res.statusmsg = str;
CU(LMTP_OK);
CUS:
lmtp_response(lmtp, &res);
return rc;
}
static void lmtp_gfs_ns(struct ns *ns)
{
if (ns->nntp != NULL) {
nntp_destroy(ns->nntp);
ns->nntp = NULL;
}
if (ns->sa != NULL) {
sa_destroy(ns->sa);
ns->sa = NULL;
}
if (ns->saa != NULL) {
sa_addr_destroy(ns->saa);
ns->saa = NULL;
}
}
static void lmtp_gfs_lhlo(lmtp2nntp_t *ctx)
{
int i;
logbook(ctx->l2, L2_LEVEL_TRACE, "LMTP service LHLO command - graceful shutdown");
for (i = 0; i < ctx->nns; i++)
lmtp_gfs_ns(&ctx->pns[i]);
if (ctx->option_mailfrom != NULL)
free(ctx->option_mailfrom);
if (ctx->saClientbind != NULL)
sa_destroy(ctx->saClientbind);
if (ctx->saaClientbind != NULL)
sa_addr_destroy(ctx->saaClientbind);
}
static int helo_rfc0821domain(char *msg, char **domain)
{
int rc;
rc = str_parse(msg,
"^.+ ("
/*
##
## The mega Perl regular expression below is generated
## with the following Perl program. This is only possible
## because the given grammar is Chomsky-3 (right or left
## linear grammar, but noth both).
##
# BNF grammar for <domain> according to RFC0821:
# <snum> ::= one, two, or three digits representing a decimal integer value in the range 0 through 255
# <a> ::= any one of the 52 alphabetic characters A through Z in upper case and a through z in lower case
# <d> ::= any one of the ten digits 0 through 9
# <let-dig-hyp> ::= <a> | <d> | "-"
# <let-dig> ::= <a> | <d>
# <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
# <dotnum> ::= <snum> "." <snum> "." <snum> "." <snum>
# <number> ::= <d> | <d> <number>
# <name> ::= <a> <ldh-str> <let-dig>
# <element> ::= <name> | "#" <number> | "[" <dotnum> "]"
# <domain> ::= <element> | <element> "." <domain>
#
# corresponding Perl regular expression ($domain)
$snum = "(?:[0-9]|[0-9]{2}|[0-1][0-9]{2}|2[0-4][0-9]|25[0-5])";
$d = "[0-9]";
$a = "[A-Za-z]";
$let_dig_hyp = "(?:$a|$d|-)";
$let_dig = "(?:$a|$d)";
$ldh_str = "${let_dig_hyp}+";
$dotnum = "$snum\\.$snum\\.$snum\\.$snum";
$number = "$d+";
$name = "$a$ldh_str$let_dig";
$element = "(?:$name|#$number|\\[$dotnum\\])";
$domain = "(?:$element\.)*$element";
#
# translate into C string block suitable for passing to the Perl
# Compatible Regular Expressions (PCRE) based string library Str.
my $cregex = $domain;
$cregex .= "\n";
$cregex =~ s|\\|\\\\|sg;
$cregex =~ s|(.{17})|$1\n|sg;
$cregex =~ s|([^\n]+)\n|"$1"\n|sg;
$cregex =~ s|\n\n|\n|sg;
print "$cregex";
*/
"(?:(?:[A-Za-z](?:[A-Za-z]|[0-9]|-)+(?:[A-Za-z]|[0-9])|#[0-9]+|\\[(?:[0"
"-9]|[0-9]{2}|[0-1][0-9]{2}|2[0-4][0-9]|25[0-5])\\.(?:[0-9]|[0-9]{2}|[0"
"-1][0-9]{2}|2[0-4][0-9]|25[0-5])\\.(?:[0-9]|[0-9]{2}|[0-1][0-9]{2}|2[0"
"-4][0-9]|25[0-5])\\.(?:[0-9]|[0-9]{2}|[0-1][0-9]{2}|2[0-4][0-9]|25[0-5"
"])\\]).)*(?:[A-Za-z](?:[A-Za-z]|[0-9]|-)+(?:[A-Za-z]|[0-9])|#[0-9]+|\\"
"[(?:[0-9]|[0-9]{2}|[0-1][0-9]{2}|2[0-4][0-9]|25[0-5])\\.(?:[0-9]|[0-9]"
"{2}|[0-1][0-9]{2}|2[0-4][0-9]|25[0-5])\\.(?:[0-9]|[0-9]{2}|[0-1][0-9]{"
"2}|2[0-4][0-9]|25[0-5])\\.(?:[0-9]|[0-9]{2}|[0-1][0-9]{2}|2[0-4][0-9]|"
"25[0-5])\\])"
")$", domain);
return rc;
}
static int helo_rfc1035domain(char *msg, char **domain)
{
int rc;
rc = str_parse(msg,
"^.+ ("
/*
##
## The mega Perl regular expression below is generated
## with the following Perl program. This is only possible
## because the given grammar is Chomsky-3 (right or left
## linear grammar, but noth both).
##
# BNF grammar for <domain> according to RFC1035:
# <letter> ::= any one of the 52 alphabetic characters A through Z in upper case and a through z in lower case
# <digit> ::= any one of the ten digits 0 through 9
# <let-dig> ::= <letter> | <digit>
# <let-dig-hyp> ::= <let-dig> | "-"
# <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
# <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
# <subdomain> ::= <label> | <subdomain> "." <label>
# <domain> ::= <subdomain> | " "
#
# corresponding Perl regular expression ($domain)
$letter = "[A-Za-z]";
$digit = "[0-9]";
$let_dig = "(?:$letter|$digit)";
$let_dig_hyp = "(?:$let_dig|-)";
$ldh_str = "${let_dig_hyp}+";
$label = "(?:$letter(?:(?:$ldh_str)?$let_dig)?)";
$subdomain = "(?:$label\.)*$label";
$domain = "(?:$subdomain| )";
#
# translate into C string block suitable for passing to the Perl
# Compatible Regular Expressions (PCRE) based string library Str.
my $cregex = $domain;
$cregex .= "\n";
$cregex =~ s|\\|\\\\|sg;
$cregex =~ s|(.{17})|$1\n|sg;
$cregex =~ s|([^\n]+)\n|"$1"\n|sg;
$cregex =~ s|\n\n|\n|sg;
print "$cregex";
*/
"(?:(?:(?:[A-Za-z](?:(?:(?:(?:[A-Za-z]|[0-9])|-)+)?(?:[A-Za-z]|[0-9]))?"
").)*(?:[A-Za-z](?:(?:(?:(?:[A-Za-z]|[0-9])|-)+)?(?:[A-Za-z]|[0-9]))?)|"
" )"
")$", domain);
return rc;
}
static lmtp_rc_t lmtp_cb_mail(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx)
{
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
lmtp_rc_t rc;
lmtp_res_t res;
logbook(ctx->l2, L2_LEVEL_INFO, "LMTP service executing MAIL command < %s", req->msg);
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 553 Requested action not taken: mailbox name not allowed
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.1.8 Bad sender's system address
*/
logbook(ctx->l2, L2_LEVEL_TRACE, "checking for previous LHLO");
if (!ctx->session.lhlo_seen) {
res.statuscode = "553";
res.dsncode = "5.1.8";
res.statusmsg = "friendly people say LHLO to open a transmission channel.";
CU(LMTP_OK);
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 503 Bad sequence of commands
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.5.0 Other or undefined protocol status
*/
logbook(ctx->l2, L2_LEVEL_TRACE, "checking for previous MAIL");
if (ctx->msg != NULL) {
res.statuscode = "503";
res.dsncode = "5.5.0";
res.statusmsg = "Sender already specified.";
CU(LMTP_OK);
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 452 Requested action not taken: insufficient system storage
* RFC1893 2. Status Codes 4.X.X Persistent Transient Failure
* RFC1893 3.5 Network and Routing Status X.3.1 Mail system full
*/
logbook(ctx->l2, L2_LEVEL_TRACE, "msg_create");
ctx->msgcount++;
if ((ctx->msg = msg_create(ctx->prival)) == NULL) {
res.statuscode = "452";
res.dsncode = "4.3.1";
res.statusmsg = "Internal error - memory.";
CU(LMTP_ERR_MEM);
}
ctx->msg->l2 = ctx->l2;
/* RFC1652 2. Framework for the 8bit MIME Transport Extension
* (4) one optional parameter using the keyword BODY is added to the MAIL
* FROM command. The value associated with this parameter is a keyword
* indicating whether a 7bit message [...] or a MIME message [...] is
* being sent. The syntax of the value is as follows, using the ABNF
* notation [...]
*
* body-value ::= "7BIT" / "8BITMIME"
*
* "MAIL From:<foo@bar>"
* "MAIL From:<foo@bar> BODY=8BITMIME"
* "MAIL From:<foo@bar> BODY=7BIT"
*
* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 553 Requested action not taken: mailbox name not allowed
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.1.7 Bad sender's mailbox address syntax
*/
logbook(ctx->l2, L2_LEVEL_TRACE, "checking if sender address is a domain name");
if (str_parse(req->msg, "m/^MAIL From:\\s*<(?:.+@.+)>/i") <= 0) {
res.statuscode = "553";
res.dsncode = "5.1.7";
res.statusmsg = "Domain name required for sender address.";
CU(LMTP_OK);
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 501 Syntax error in parameters or arguments
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.5.4 Invalid command arguments
*/
logbook(ctx->l2, L2_LEVEL_TRACE, "checking BODY keyword");
if (str_parse(req->msg, "m/^MAIL From:\\s*<(.+@.+)>"
"(?:\\s+BODY=(?:7BIT|8BITMIME)\\s*)?$/i",
&ctx->msg->mail_from) <= 0) {
res.statuscode = "501";
res.dsncode = "5.5.4";
res.statusmsg = "Unknown parameter for keyword BODY.";
CU(LMTP_OK);
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 550 Requested action not taken: mailbox unavailable
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.7.1 Delivery not authorized, message refused
*/
logbook(ctx->l2, L2_LEVEL_TRACE, "checking if sender is allowed");
if (ctx->option_mailfrom != NULL) {
logbook(ctx->l2, L2_LEVEL_TRACE, "\"%s\" matching against \"%s\"", ctx->msg->mail_from, ctx->option_mailfrom);
if (str_parse(ctx->msg->mail_from, ctx->option_mailfrom) <= 0) {
res.statuscode = "550";
res.dsncode = "5.7.1";
res.statusmsg = "Delivery not authorized, message refused.";
CU(LMTP_OK);
}
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 250 Requested mail action okay, completed
* RFC1893 2. Status Codes 2.X.X Success
* RFC1893 3.5 Network and Routing Status X.1.0 Other address status
*/
res.statuscode = "250";
res.dsncode = "2.1.0";
res.statusmsg = "Sender ok.";
lmtp_response(lmtp, &res);
return LMTP_OK;
CUS:
lmtp_response(lmtp, &res);
if (ctx->msg != NULL) {
msg_destroy(ctx->msg);
ctx->msg = NULL;
}
return rc;
}
static lmtp_rc_t lmtp_cb_rcpt(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx)
{
lmtp_res_t res;
lmtp_rc_t rc;
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
char *cp = NULL;
char *group;
logbook(ctx->l2, L2_LEVEL_INFO, "LMTP service executing RCPT command < %s", req->msg);
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 503 Bad sequence of commands
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.5.0 Other or undefined protocol status
*/
logbook(ctx->l2, L2_LEVEL_TRACE, "checking for previous MAIL");
if ((ctx->msg == NULL) || (ctx->msg->mail_from == NULL)) {
res.statuscode = "503";
res.dsncode = "5.5.0";
res.statusmsg = "specify sender with MAIL first.";
CU(LMTP_OK);
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 501 Syntax error in parameters or arguments
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.5.2 Syntax error
*/
logbook(ctx->l2, L2_LEVEL_TRACE, "checking parameter syntax");
if (str_parse(req->msg, "m/^RCPT To:\\s*(.+)$/i", &cp) <= 0) {
res.statuscode = "501";
res.dsncode = "5.5.2";
res.statusmsg = "Syntax error in parameters.";
CU(LMTP_OK);
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 550 Requested action not taken: mailbox unavailable
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.1.1 Bad destination mailbox address
*/
logbook(ctx->l2, L2_LEVEL_TRACE, "checking for empty parameter");
if ((cp == NULL) || (strlen(cp) == 0)) {
res.statuscode = "550";
res.dsncode = "5.1.1";
res.statusmsg = "empty Recipient/ Group.";
CU(LMTP_OK);
}
/* in GROUPMODE = ARG|HEADER recipient must be acknowledged and stored to
* give proper pipelining responses. in GROUPMODE = ENVELOPE recipient is
* transformed into a group and matched against groupfilter. Only valid
* groups are stored to give proper pipelining responses.
*
* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 550 Requested action not taken: mailbox unavailable
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.1.1 Bad destination mailbox address
* X.7.2 Mailing list expansion prohibited
*/
logbook(ctx->l2, L2_LEVEL_DEBUG, "ctx->option_groupmode=%d", ctx->option_groupmode);
if (ctx->option_groupmode == GROUPMODE_ENVELOPE) {
logbook(ctx->l2, L2_LEVEL_TRACE, "groupmode=envelope; transform recipient into group");
if (str_parse(cp, "m/^<(.+)?@[^@]+>$/i", &group) <= 0) {
res.statuscode = "550";
res.dsncode = "5.1.1";
res.statusmsg = "Recipient did not transform into group.";
CU(LMTP_OK);
}
logbook(ctx->l2, L2_LEVEL_TRACE, "groupmode=envelope; match group %s", group);
if (!groupmatch(ctx->azGroupargs, ctx->asGroupargs, group)) {
res.statuscode = "550";
res.dsncode = "5.7.2";
res.statusmsg = "unmatched Group.";
CU(LMTP_OK);
}
logbook(ctx->l2, L2_LEVEL_TRACE, "memorize group %s", group);
argz_add(&ctx->msg->azEnvgroups, &ctx->msg->asEnvgroups, group);
}
logbook(ctx->l2, L2_LEVEL_TRACE, "memorize recipient %s", cp);
argz_add(&ctx->msg->azRcpt, &ctx->msg->asRcpt, cp);
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 250 Requested mail action okay, completed
* RFC1893 2. Status Codes 2.X.X Success
* RFC1893 3.5 Network and Routing Status X.1.5 Destination address valid
*/
res.statuscode = "250";
res.dsncode = "2.1.5";
res.statusmsg = ctx->option_groupmode == GROUPMODE_ENVELOPE ? "Group accepted." : "Recipient accepted.";
CU(LMTP_OK);
CUS:
if (cp != NULL)
free(cp);
lmtp_response(lmtp, &res);
return rc;
}
int groupmatch(char *azPattern, size_t asPattern, char *cpGroup)
{
int bGroupmatch;
char *cpGroupmatch;
bGroupmatch = FALSE;
cpGroupmatch = NULL;
while ((cpGroupmatch = argz_next(azPattern, asPattern, cpGroupmatch)) != NULL) {
if (shpat_match(cpGroupmatch, cpGroup, 0) == 0)
bGroupmatch = TRUE;
}
return bGroupmatch;
}
static lmtp_rc_t lmtp_cb_data(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx)
{
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
lmtp_rc_t rc = LMTP_OK;
lmtp_res_t res;
char *azErr;
size_t asErr;
char errorstring[STDSTRLEN];
char *rcpt;
int i;
int bSuccess;
char *cp;
int bOk;
char *cpRestrictheader;
char *cpRestrictvalue;
logbook(ctx->l2, L2_LEVEL_INFO, "LMTP service executing DATA command < %s", req->msg);
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 503 Bad sequence of commands
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.5.0 Other or undefined protocol status
*/
logbook(ctx->l2, L2_LEVEL_TRACE, "checking for previous RCPT");
if ((ctx->msg == NULL) || (argz_count(ctx->msg->azRcpt, ctx->msg->asRcpt) == 0)) {
res.statuscode = "503";
res.dsncode = "5.5.0";
res.statusmsg = "specify recipient with RCPT first.";
lmtp_response(lmtp, &res);
return LMTP_OK;
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 354 Start mail input; end with <CRLF>.<CRLF>
*/
logbook(ctx->l2, L2_LEVEL_TRACE, "tell remote to send message now");
res.statuscode = "354";
res.dsncode = NULL; /* DSN not used for data */
res.statusmsg = "Enter mail, end with \".\" on a line by itself";
lmtp_response(lmtp, &res);
logbook(ctx->l2, L2_LEVEL_TRACE, "read message with maximum size to accept = %d", ctx->option_maxmessagesize);
rc = lmtp_readmsg(lmtp, &ctx->msg->cpMsg, ctx->option_maxmessagesize);
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 552 Requested mail action aborted: exceeded storage allocation
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.2.3 Message length exceeds administrative limit.
*/
logbook(ctx->l2, L2_LEVEL_TRACE, "checking for excessive message size");
if (rc == LMTP_ERR_OVERFLOW) {
str_format(errorstring, sizeof(errorstring), "Message length exceeds administrative limit. %s", lmtp_error(rc));
res.statuscode = "552";
res.dsncode = "5.2.3";
res.statusmsg = errorstring;
rcpt = NULL;
while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
lmtp_response(lmtp, &res);
}
return LMTP_OK;
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 451 Requested action aborted: local error in processing
* RFC1893 2. Status Codes 4.X.X Persistent Transient Failure
* RFC1893 3.5 Network and Routing Status X.3.2 System not accepting network messages
*/
logbook(ctx->l2, L2_LEVEL_TRACE, "checking for system error");
if (rc == LMTP_ERR_SYSTEM) {
str_format(errorstring, sizeof(errorstring), "System error reading message: %s", strerror(errno));
res.statuscode = "451";
res.dsncode = "4.3.2";
res.statusmsg = errorstring;
rcpt = NULL;
while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
lmtp_response(lmtp, &res);
}
return LMTP_OK;
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 451 Requested action aborted: local error in processing
* RFC1893 2. Status Codes 4.X.X Persistent Transient Failure
* RFC1893 3.5 Network and Routing Status X.3.2 System not accepting network messages
*/
logbook(ctx->l2, L2_LEVEL_TRACE, "checking for other error");
if(rc != LMTP_OK) {
str_format(errorstring, sizeof(errorstring), "Unknown error reading message: %s", lmtp_error(rc));
res.statuscode = "451";
res.dsncode = "4.3.2";
res.statusmsg = errorstring;
rcpt = NULL;
while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
lmtp_response(lmtp, &res);
}
return LMTP_OK;
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 554 Transaction failed
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.6.5 Conversion Failed
*/
logbook(ctx->l2, L2_LEVEL_TRACE, "split message");
if ((rc = msg_split(ctx->msg)) != MSG_OK) {
str_format(errorstring, sizeof(errorstring), "Error splitting message: %s", msg_error(rc));
res.statuscode = "554";
res.dsncode = "5.6.5";
res.statusmsg = errorstring;
rcpt = NULL;
while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
lmtp_response(lmtp, &res);
}
return LMTP_OK;
}
if (ctx->option_groupmode == GROUPMODE_ENVELOPE) {
if ((cp = malloc(ctx->msg->asEnvgroups + 1)) == NULL) {
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 452 Requested action not taken: insufficient system storage
* RFC1893 2. Status Codes 4.X.X Persistent Transient Failure
* RFC1893 3.5 Network and Routing Status X.3.1 Mail system full
*/
ctx->msgcount++;
if ((ctx->msg = msg_create(ctx->prival)) == NULL) {
res.statuscode = "452";
res.dsncode = "4.3.1";
res.statusmsg = "Internal error - memory.";
lmtp_response(lmtp, &res);
return LMTP_ERR_MEM;
}
}
ctx->msg->azNewsgroups = memcpy(cp, ctx->msg->azEnvgroups != NULL ? ctx->msg->azEnvgroups : "", ctx->msg->asEnvgroups);
ctx->msg->asNewsgroups = ctx->msg->asEnvgroups;
}
else if (ctx->option_groupmode == GROUPMODE_ARG) {
if ((cp = malloc(ctx->asGroupargs + 1)) == NULL) {
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 452 Requested action not taken: insufficient system storage
* RFC1893 2. Status Codes 4.X.X Persistent Transient Failure
* RFC1893 3.5 Network and Routing Status X.3.1 Mail system full
*/
ctx->msgcount++;
if ((ctx->msg = msg_create(ctx->prival)) == NULL) {
res.statuscode = "452";
res.dsncode = "4.3.1";
res.statusmsg = "Internal error - memory.";
lmtp_response(lmtp, &res);
return LMTP_ERR_MEM;
}
}
ctx->msg->azNewsgroups = memcpy(cp, ctx->azGroupargs != NULL ? ctx->azGroupargs : "", ctx->asGroupargs);
ctx->msg->asNewsgroups = ctx->asGroupargs;
}
else { /* == GROUPMODE_HEADER */
cp = ctx->msg->azNewsgroups;
while (cp != NULL) {
if (!groupmatch(ctx->azGroupargs, ctx->asGroupargs, cp)) {
if (argz_next(ctx->msg->azNewsgroups, ctx->msg->asNewsgroups, cp) == NULL) {
argz_delete(&ctx->msg->azNewsgroups, &ctx->msg->asNewsgroups, cp);
break;
}
else
argz_delete(&ctx->msg->azNewsgroups, &ctx->msg->asNewsgroups, cp);
} else {
cp = argz_next(ctx->msg->azNewsgroups, ctx->msg->asNewsgroups, cp);
}
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 550 Requested action not taken: mailbox unavailable
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.7.2 Mailing list expansion prohibited
*/
if (ctx->msg->asNewsgroups == 0) {
rcpt = NULL;
while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
res.statuscode = "550";
res.dsncode = "5.7.2";
res.statusmsg = "Header did not match any valid group.";
lmtp_response(lmtp, &res);
}
return LMTP_OK;
}
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 550 Requested action not taken: mailbox unavailable
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.7.1 Delivery not authorized, message refused
*/
logbook(ctx->l2, L2_LEVEL_TRACE, "checking if restricted header causes reject");
if (ctx->option_restrictheader != NULL) {
bOk = FALSE;
cp = NULL;
while ((cp = argz_next(ctx->msg->azHeaders, ctx->msg->asHeaders, cp)) != NULL) {
cpRestrictheader = cp;
if ((cp = argz_next(ctx->msg->azHeaders, ctx->msg->asHeaders, cp)) == NULL)
break;
cpRestrictvalue = cp;
str_format(errorstring, sizeof(errorstring), "%s %s", cpRestrictheader, cpRestrictvalue);
if (str_parse(errorstring, ctx->option_restrictheader) <= 0) {
logbook(ctx->l2, L2_LEVEL_TRACE, "\"%s\" matching against \"%s\" NO", errorstring, ctx->option_restrictheader);
}
else {
logbook(ctx->l2, L2_LEVEL_TRACE, "\"%s\" matching against \"%s\": YES", errorstring, ctx->option_restrictheader);
bOk = TRUE;
break;
}
}
if (bOk) {
logbook(ctx->l2, L2_LEVEL_TRACE, "restricted header found");
rcpt = NULL;
while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
res.statuscode = "550";
res.dsncode = "5.7.1";
res.statusmsg = "Restricted header matched, message rejected.";
lmtp_response(lmtp, &res);
}
return LMTP_OK;
}
}
/* rewrite headers */
logbook(ctx->l2, L2_LEVEL_TRACE, "appying header rewrite rules");
msg_headermatrixbuildup(ctx->msg);
headerrewrite(ctx);
msg_headermatrixteardwn(ctx->msg);
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 554 Transaction failed
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.6.5 Conversion Failed
*/
logbook(ctx->l2, L2_LEVEL_TRACE, "join message");
if ((rc = msg_join(ctx->msg)) != MSG_OK) {
str_format(errorstring, sizeof(errorstring), "Error joining message: %s", msg_error(rc));
res.statuscode = "554";
res.dsncode = "5.6.5";
res.statusmsg = errorstring;
rcpt = NULL;
while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
lmtp_response(lmtp, &res);
}
return LMTP_OK;
}
logbook(ctx->l2, L2_LEVEL_TRACE, "deliver message");
bSuccess = NNTP_ERR_DELIVERY; /* assume a hard error for the worst case */
for (i = 0; i < ctx->nns; i++) {
switch (ctx->option_operationmode) {
case OPERATIONMODE_FAKE:
ctx->pns[i].rc = NNTP_FAKE;
break;
case OPERATIONMODE_POST:
if (ctx->pns[i].nntp)
ctx->pns[i].rc = nntp_post(ctx->pns[i].nntp, ctx->msg);
break;
case OPERATIONMODE_FEED:
if (ctx->pns[i].nntp)
ctx->pns[i].rc = nntp_feed(ctx->pns[i].nntp, ctx->msg);
break;
}
if (ctx->pns[i].rc == NNTP_OK)
bSuccess = NNTP_OK;
if ( bSuccess != NNTP_OK
&& (
(ctx->pns[i].rc == NNTP_ERR_SYSTEM)
|| (ctx->pns[i].rc == NNTP_DEFER)
)
)
bSuccess = NNTP_DEFER;
}
if (ctx->option_operationmode == OPERATIONMODE_FAKE) {
str_format(errorstring, sizeof(errorstring),
"NNTP running in fake mode, delivery of %s [%d bytes] %s but delivery status forced to",
ctx->msg->cpMsgid,
strlen(ctx->msg->cpMsg),
((bSuccess == NNTP_OK) ? "succeeded" :
(bSuccess == NNTP_DEFER) ? "deferred" : "failed"));
switch (ctx->option_operationmodefakestatus[0]) {
case '5':
bSuccess = NNTP_ERR_UNKNOWN;
logbook(ctx->l2, L2_LEVEL_NOTICE, "%s %s", errorstring, "failed");
break;
case '4':
bSuccess = NNTP_DEFER;
logbook(ctx->l2, L2_LEVEL_NOTICE, "%s %s", errorstring, "deferred");
break;
default:
bSuccess = NNTP_OK;
logbook(ctx->l2, L2_LEVEL_NOTICE, "%s %s", errorstring, "succeeded");
break;
}
} else {
str_format(errorstring, sizeof(errorstring), "%sdelivery of %s [%d bytes]",
((ctx->option_operationmode == OPERATIONMODE_POST) ? "post " :
(ctx->option_operationmode == OPERATIONMODE_FEED) ? "feed " : ""),
ctx->msg->cpMsgid,
strlen(ctx->msg->cpMsg));
if (bSuccess == NNTP_OK)
logbook(ctx->l2, L2_LEVEL_NOTICE, "%s %s", errorstring, "succeeded");
else if(bSuccess == NNTP_DEFER)
logbook(ctx->l2, L2_LEVEL_WARNING, "%s %s", errorstring, "deferred");
else
logbook(ctx->l2, L2_LEVEL_ERROR, "%s %s", errorstring, "failed");
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 250 Requested mail action okay, completed
* 451 Requested action aborted: local error in processing
* 554 Transaction failed
* RFC1893 2. Status Codes 2.X.X Success
* 4.X.X Persistent Transient Failure
* 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.0.0 Other undefined Status
* X.4.2 Bad connection
*/
rcpt = NULL;
while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
logbook(ctx->l2, L2_LEVEL_DEBUG, "ctx->option_operationmode=%d", ctx->option_operationmode);
if (ctx->option_operationmode == OPERATIONMODE_FAKE) {
res.statuscode = ctx->option_operationmodefakestatus;
res.dsncode = ctx->option_operationmodefakedsn;
str_format(errorstring, sizeof(errorstring),
"NNTP noop fake return for %s", rcpt);
} else {
switch (bSuccess) {
case NNTP_OK:
str_format(errorstring, sizeof(errorstring),
"Message accepted for delivery to %s", rcpt);
res.statuscode = "250";
res.dsncode = "2.0.0";
break;
case NNTP_DEFER:
str_format(errorstring, sizeof(errorstring),
"Requested action aborted for %s, local error in processing.", rcpt);
res.statuscode = "451";
res.dsncode = "4.4.2";
break;
default:
str_format(errorstring, sizeof(errorstring),
"Error sending article for %s.", rcpt);
res.statuscode = "554";
res.dsncode = "5.4.2";
break;
}
}
azErr = NULL;
asErr = 0;
argz_add(&azErr, &asErr, errorstring);
for (i = 0; i < ctx->nns; i++) {
if (ctx->pns[i].rc != NNTP_OK) {
var_rc_t var_rc;
char *res_ptr;
size_t res_len;
str_format(errorstring, sizeof(errorstring),
"destination#%d ${option.destination[%d]} returned %s\n"
"destination#%d ${option.destination[%d]} lastresp \"%s\"",
i, i, nntp_error(ctx->pns[i].rc),
i, i, nntp_lastresp(ctx->pns[i].nntp));
if ((var_rc = var_expand(ctx->config_varctx, errorstring, strlen(errorstring), &res_ptr, &res_len, FALSE)) != VAR_OK) {
str_format(errorstring, sizeof(errorstring),
"destination#%d returned %s\n"
"destination#%d lastresp \"%s\"",
i, nntp_error(ctx->pns[i].rc),
i, nntp_lastresp(ctx->pns[i].nntp));
}
else {
str_copy(errorstring, res_ptr, sizeof(errorstring) < res_len ? sizeof(errorstring) : res_len);
free(res_ptr);
}
argz_add(&azErr, &asErr, errorstring);
}
}
if (azErr != NULL) {
argz_stringify(azErr, asErr, '\n');
res.statusmsg = azErr;
lmtp_response(lmtp, &res);
free(azErr);
azErr = NULL;
asErr = 0;
}
else {
res.statusmsg = errorstring;
lmtp_response(lmtp, &res);
}
}
msg_destroy(ctx->msg);
ctx->msg = NULL;
return LMTP_OK;
}
static lmtp_rc_t lmtp_cb_noop(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx)
{
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
lmtp_res_t res;
lmtp_rc_t rc = LMTP_OK;
logbook(ctx->l2, L2_LEVEL_INFO, "LMTP service executing NOOP command < %s", req->msg);
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 250 Requested mail action okay, completed
* RFC1893 2. Status Codes 2.X.X Success
* RFC1893 3.5 Network and Routing Status X.0.0 Other undefined Status
*/
res.statuscode = "250";
res.dsncode = "2.0.0";
res.statusmsg = "OK. Nice talking to you.";
lmtp_response(lmtp, &res);
return rc;
}
static lmtp_rc_t lmtp_cb_rset(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx)
{
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
lmtp_res_t res;
lmtp_rc_t rc = LMTP_OK;
logbook(ctx->l2, L2_LEVEL_INFO, "LMTP service executing RSET command < %s", req->msg);
lmtp_gfs_rset(ctx);
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 250 Requested mail action okay, completed
* RFC1893 2. Status Codes 2.X.X Success
* RFC1893 3.5 Network and Routing Status X.0.0 Other undefined Status
*/
res.statuscode = "250";
res.dsncode = "2.0.0";
res.statusmsg = "Reset state.";
lmtp_response(lmtp, &res);
return rc;
}
static void lmtp_gfs_rset(lmtp2nntp_t *ctx)
{
logbook(ctx->l2, L2_LEVEL_TRACE, "LMTP service RSET command - graceful shutdown");
if (ctx->msg != NULL) {
msg_destroy(ctx->msg);
ctx->msg = NULL;
}
}
static lmtp_rc_t lmtp_cb_quit(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx)
{
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
lmtp_res_t res;
lmtp_rc_t rc = LMTP_EOF;
logbook(ctx->l2, L2_LEVEL_INFO, "LMTP service executing QUIT command < %s", req->msg);
lmtp_gfs_quit(ctx);
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 221 <domain> Service closing transmission channel
* RFC1893 2. Status Codes 2.X.X Success
* RFC1893 3.5 Network and Routing Status X.0.0 Other undefined Status
*/
res.statuscode = "221";
res.dsncode = "2.0.0";
res.statusmsg = "LMTP Service closing transmission channel.";
lmtp_response(lmtp, &res);
return rc;
}
static void lmtp_gfs_quit(lmtp2nntp_t *ctx)
{
logbook(ctx->l2, L2_LEVEL_TRACE, "LMTP service QUIT command - graceful shutdown");
lmtp_gfs_rset(ctx);
resetsession(&ctx->session);
}