/*
** Copyright (c) 2001 The OSSP Project
** Copyright (c) 2001 Cable & Wireless Deutschland
**
** 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/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 .
**
** lmtp2nntp.c: LMTP to NNTP main procedure
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* third party */
#include "str.h"
#include "argz.h"
#include "shpat_match.h"
#include "l2.h"
#include "var.h"
#include "daemon.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.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#if defined(HAVE_DMALLOC_H) && defined(DMALLOC)
#include "dmalloc.h"
#endif
#include "lmtp.h"
#include "nntp.h"
#include "sa.h"
#include "msg.h"
#define _VERSION_C_AS_HEADER_
#include "version.c"
#undef _VERSION_C_AS_HEADER_
#ifndef FALSE
#define FALSE (1 != 1)
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif
#ifndef NUL
#define NUL '\0'
#endif
#define ERR_EXECUTION 1
#define ERR_DELIVERY -2
#define STDSTRLEN 512
#define MAXNEWSSERVICES 16
#define MAXACLS 32
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);
struct session {
int lhlo_seen;
char *lhlo_domain;
};
static void catchsignal(int sig, ...);
static void initsession(struct session *session);
static void resetsession(struct session *session);
int groupmatch(char *, size_t, char *);
struct ns {
char *h; /* host */
char *p; /* port */
sa_addr_t *saa; /* socket address abstraction */
sa_t *sa; /* socket abstraction */
nntp_t *nntp;
nntp_rc_t rc;
l2_channel_t *l2;
};
struct acl {
char *acl;
int not;
sa_addr_t *saa;
size_t prefixlen;
};
typedef struct {
l2_context_t ctx;
char *progname;
char *option_logfile;
int option_groupmode;
int option_operationmode;
char *option_operationmodefakestatus;
char *option_operationmodefakedsn;
int option_maxmessagesize;
char *azHeaderValuePairs;
size_t asHeaderValuePairs;
int option_timeout_lmtp_accept;
int option_timeout_lmtp_read;
int option_timeout_lmtp_write;
int option_timeout_nntp_connect;
int option_timeout_nntp_read;
int option_timeout_nntp_write;
char *option_mailfrom;
char *option_restrictheader;
unsigned int option_levelmask;
char *option_pidfile;
int option_killflag;
uid_t option_uid;
int option_daemon;
int option_aclc;
struct acl option_acl[MAXACLS];
int option_veryverbose;
int option_childsmax;
int active_childs;
l2_env_t *l2_env;
l2_channel_t *l2;
sa_addr_t *saaAltio;
sa_t *saAltio;
char *cpBindh;
char *cpBindp;
sa_addr_t *saaBind;
sa_t *saBind;
sa_addr_t *saaIO;
sa_t *saIO;
int fdIOi;
int fdIOo;
int nsc;
struct ns ns[MAXNEWSSERVICES];
char *azGroupargs;
size_t asGroupargs;
struct session session;
msg_t *msg;
struct utsname uname;
} lmtp2nntp_t;
static var_config_t ctx_lookup_cfg = {
'$', /* varinit */
'{', /* startdelim */
'}', /* enddelim */
'[', /* startindex */
']', /* endindex */
'#', /* current_index */
'\\', /* escape */
"a-zA-Z0-9_.-" /* namechars */
};
static var_rc_t ctx_lookup(
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;
const char *name;
size_t len;
size_t n;
char *cp;
var_rc_t rc;
log2(ctx, DEBUG, "lookup variable \"%s\" (%d)",
var_ptr /* FIXME: NUL-termination? */, var_len);
rc = VAR_ERR_UNDEFINED_VARIABLE;
if (strncasecmp(var_ptr, "lmtp2nntp.version", var_len) == 0) {
}
else if (strncasecmp(var_ptr, "os.name", var_len) == 0) {
}
else if (strncasecmp(var_ptr, "os.version", var_len) == 0) {
}
else if (var_len > 8 && strncasecmp(var_ptr, "msg.hdr.", 8) == 0) {
name = var_ptr + 8;
len = strlen(name);
if (ctx == NULL)
return VAR_ERR_UNDEFINED_VARIABLE;
if (ctx->msg == NULL)
return VAR_ERR_UNDEFINED_VARIABLE;
if (ctx->msg->azHeaders == NULL)
return VAR_ERR_UNDEFINED_VARIABLE;
cp = NULL;
while ((cp = argz_next(ctx->msg->azHeaders, ctx->msg->asHeaders, cp)) != NULL) {
char *cpVar, *cpVal;
int nVar, nVal;
cpVar = cp;
nVar = strlen(cpVar);
if ((cp = argz_next(ctx->msg->azHeaders, ctx->msg->asHeaders, cp)) == NULL)
break;
cpVal = cp;
nVal = strlen(cpVal);
if (len == (nVar-1) && strncasecmp(cpVar, name, len) == 0 && cpVar[len] == ':') {
*val_ptr = cpVal;
*val_len = nVal;
*val_size = 0;
rc = VAR_OK;
}
}
}
if (rc == VAR_OK)
log4(ctx, DEBUG, "lookup variable \"%s\" (%d) ok: result is \"%s\" (%d)",
var_ptr /* FIXME: NUL-termination? */, var_len, *val_ptr, *val_len);
else
log3(ctx, DEBUG, "lookup variable \"%s\" (%d) failed: %s",
var_ptr /* FIXME: NUL-termination? */, var_len, var_strerror(rc));
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 *);
enum {
GROUPMODE_ARG,
GROUPMODE_ENVELOPE,
GROUPMODE_HEADER
};
enum {
OPERATIONMODE_FAKE,
OPERATIONMODE_POST,
OPERATIONMODE_FEED
};
/*
* print usage information
*/
static void usage(char *command)
{
/* use
* perl ) { if(m/\/\*POD (.*) .*\*\//) { $_=$1; s/.<(.*?)>/$1/g ; print "\"$_ \"\n" };}'
* to pull the USAGE string out of this source
*/
fprintf(stderr,
"USAGE: %s "
"[-C childsmax] "
"[-D] "
"[-K] "
"[-P pidfile] "
"[-V] "
"[-a addr/mask[,addr/mask[,...]] "
"[-b addr[:port]|-|path[:perms]] "
"[-c addr[:port]] "
"[-d addr[:port][,addr[:port], ...]] "
"[-g groupmode] "
"[-h header:value] "
"[-l level[:logfile]] "
"[-m mailfrom] "
"[-n nodename] "
"[-o operationmode] "
"[-r restrictheader] "
"[-s size] "
"[-t name=sec[,name=sec[,...]] "
"[-u uid] "
"[-v] "
"newsgroup [newsgroup ...] "
"\n",
command);
return;
}
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)
rc = -1;
else
rc = (ssize_t)n;
}
else
rc = read(ctx->fdIOi, buf, nbytes);
if (rc == -1)
log0(ctx, TRACE, "LMTP read error: %m");
else
log3(ctx, TRACE, "LMTP %5d << \"%{text}D\"", rc, buf, rc);
log1(ctx, 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;
log3(ctx, TRACE, "LMTP %5d >> \"%{text}D\"", nbytes, buf, nbytes);
if (ctx->saIO != NULL) {
if ((rv = sa_write(ctx->saIO, buf, nbytes, &n)) != SA_OK)
rc = -1;
else
rc = (ssize_t)n;
}
else
rc = write(ctx->fdIOo, buf, nbytes);
if (rc == -1)
log0(ctx, TRACE, "LMTP write error: %m");
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)
rc = -1;
else
rc = (ssize_t)n;
if (rc == -1)
log0(ctx, TRACE, "NNTP read error: %m");
else
log3(ctx, 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;
log3(ctx, TRACE, "NNTP %5d >> \"%{text}D\"", nbytes, buf, nbytes);
if ((rv = sa_write(ctx->sa, buf, nbytes, &n)) != SA_OK)
rc = -1;
else
rc = (ssize_t)n;
if (rc == -1)
log0(ctx, TRACE, "NNTP write error: %m");
return rc;
}
static l2_result_t
formatter_prefix(l2_context_t *_ctx, const char id, const char *param,
char *bufptr, size_t bufsize, size_t *buflen, va_list *ap)
{
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx->vp;
if ((ctx->msg != NULL) && (ctx->msg->cpFid != NULL)) {
sprintf(bufptr, "%s: ", ctx->msg->cpFid);
*buflen = strlen(bufptr);
}
else
*buflen = 0;
return L2_OK;
}
static l2_result_t
formatter_errno(l2_context_t *_ctx, const char id, const char *param,
char *bufptr, size_t bufsize, size_t *buflen, va_list *ap)
{
sprintf(bufptr, "(%d) %s", errno, strerror(errno));
*buflen = strlen(bufptr);
return L2_OK;
}
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);
log0(ctx, TRACE, "catching and logging signals now");
va_end(ap);
return;
}
if (ctx != NULL) {
switch (sig) {
case SIGCHLD:
log1(ctx, NOTICE, "caught signal %d - wait for child", sig);
pid = wait(NULL);
ctx->active_childs--;
log2(ctx, NOTICE, "caught signal %d - child [%ld] terminated", sig, (long)pid);
return;
case SIGUSR1:
log1(ctx, NOTICE, "caught signal %d - flush logging stream", sig);
l2_channel_flush(ctx->l2);
return;
case SIGHUP:
case SIGINT:
case SIGQUIT:
log1(ctx, NOTICE, "caught signal %d - exit - no more logging", sig);
break;
default:
log1(ctx, 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 ... */
char *cp; /* general purpose character pointer */
char *azHosts;
size_t asHosts;
char *azTimeout;
size_t asTimeout;
char *azACL;
size_t asACL;
char *cpHost;
char *cpPort;
pid_t pid;
FILE *fd;
char *cpName;
char *cpValue;
int nValue;
char *cpAddr;
char *cpPrefixLen;
struct passwd *sPasswd;
char *cpHeadername;
char *cpHeadervalue;
/* drop effective uid/gid priviledges */
seteuid(getuid());
setegid(getgid());
/* 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 */
if ((ctx = (lmtp2nntp_t *)malloc(sizeof(lmtp2nntp_t))) == NULL)
CU(ERR_EXECUTION);
ctx->ctx.vp = ctx;
ctx->progname = strdup(argv[0]);
ctx->option_logfile = NULL;
ctx->option_groupmode = GROUPMODE_ARG;
ctx->option_operationmode = OPERATIONMODE_FAKE;
ctx->option_operationmodefakestatus = "553"; /* Requested action not taken: mailbox name not allowed */
ctx->option_operationmodefakedsn = "5.7.1"; /* Delivery not authorized, message refused */
ctx->option_maxmessagesize = 8 * 1024 * 1024;
ctx->azHeaderValuePairs = NULL;
ctx->asHeaderValuePairs = 0;
ctx->option_timeout_lmtp_accept = 0;
ctx->option_timeout_lmtp_read = 10;
ctx->option_timeout_lmtp_write = 10;
ctx->option_timeout_nntp_connect = 360;
ctx->option_timeout_nntp_read = 60;
ctx->option_timeout_nntp_write = 60;
ctx->option_mailfrom = NULL;
ctx->option_restrictheader = NULL;
ctx->option_levelmask = L2_LEVEL_NONE;
ctx->option_pidfile = NULL;
ctx->option_killflag = FALSE;
ctx->option_uid = getuid();
ctx->option_daemon = FALSE;
ctx->option_veryverbose = FALSE;
ctx->option_childsmax = 10;
ctx->active_childs = 0;
ctx->l2_env = NULL;
ctx->l2 = NULL;
ctx->saaAltio = NULL;
ctx->saAltio = NULL;
ctx->cpBindh = NULL;
ctx->cpBindp = NULL;
ctx->saaBind = NULL;
ctx->saBind = NULL;
ctx->nsc = 0;
for (i=0; i < MAXNEWSSERVICES; i++) {
ctx->ns[i].h = NULL;
ctx->ns[i].p = NULL;
ctx->ns[i].saa = NULL;
ctx->ns[i].sa = NULL;
ctx->ns[i].nntp = NULL;
ctx->ns[i].rc = LMTP_ERR_UNKNOWN;
ctx->ns[i].l2 = NULL;
}
ctx->option_aclc = 0;
for (i = 0; i < MAXACLS; i++) {
ctx->option_acl[i].acl = NULL;
ctx->option_acl[i].not = FALSE;
ctx->option_acl[i].saa = NULL;
ctx->option_acl[i].prefixlen = 0;
}
ctx->azGroupargs = NULL;
ctx->asGroupargs = 0;
initsession(&ctx->session);
ctx->msg = NULL;
if (uname(&ctx->uname) == -1) {
fprintf(stderr, "%s:Error: uname failed \"%s\"\n", ctx->progname, strerror(errno));
CU(ERR_EXECUTION);
}
/*POD B */
/* use
* perl ) { if(m/\/\*POD (.*) .*\*\//) { $_=$1; print "$_\n" };}'
* to pull the POD SYNOPSIS header directly out of this source
*/
/* read in the arguments */
while ((i = getopt(argc, argv, "C:DKP:Va:b:c:d:g:h:l:m:n:o:r:s:t:u:v")) != -1) {
switch (i) {
case 'C': /*POD [B<-C> I] */
ctx->option_childsmax = atoi(optarg);
if (ctx->option_childsmax <= 0) {
fprintf(stderr, "%s:Error: Invalid number (%d) to option -C\n", ctx->progname, ctx->option_childsmax);
CU(ERR_EXECUTION);
}
break;
case 'D': /*POD [B<-D>] */
ctx->option_daemon = TRUE;
break;
case 'K': /*POD [B<-K>] */
ctx->option_killflag = TRUE;
break;
case 'P': /*POD [B<-P> I] */
ctx->option_pidfile = strdup(optarg);
break;
case 'V': /*POD [B<-V>] */
ctx->option_veryverbose = TRUE;
break;
case 'a': /*POD [B<-a> I/I[,I/I[,...]] */
if (argz_create_sep(optarg, ',', &azACL, &asACL) != 0)
CU(ERR_EXECUTION);
cp = NULL;
while ((cp = argz_next(azACL, asACL, cp)) != NULL) {
if (ctx->option_aclc >= MAXACLS) {
fprintf(stderr, "%s:Error: Too many ACL (%d) using option -a\n", ctx->progname, ctx->option_aclc);
CU(ERR_EXECUTION);
}
ctx->option_acl[ctx->option_aclc].acl = strdup(cp);
if (cp[0] == '!') {
ctx->option_acl[ctx->option_aclc].not = TRUE;
cpAddr = strdup(cp+1);
}
else {
cpAddr = strdup(cp);
}
if ((cpPrefixLen = strrchr(cpAddr, '/')) != NULL)
*cpPrefixLen++ = NUL;
else
cpPrefixLen = "-1";
ctx->option_acl[ctx->option_aclc].prefixlen = atoi(cpPrefixLen);
if ((rc = sa_addr_create(&ctx->option_acl[ctx->option_aclc].saa)) != SA_OK) {
fprintf(stderr, "%s:Error: Creating address failed for -a option (%d)\n",
ctx->progname, rc);
}
if ((rc = sa_addr_u2a(ctx->option_acl[ctx->option_aclc].saa, "inet://%s:0", cpAddr)) != SA_OK) {
fprintf(stderr, "%s:Error: Parsing host address failed for \"%s:0\" (%d)\n",
ctx->progname, cpAddr, rc);
CU(ERR_EXECUTION);
}
ctx->option_aclc++;
free(cpAddr);
}
free(azACL);
break;
case 'b': /*POD [B<-b> I[I<:port>]|C<->|I[:perms]] */
if (strcmp(optarg, "-") != 0) {
if ((rc = sa_create(&ctx->saAltio)) != SA_OK) {
fprintf(stderr, "%s:Error: Creating TCP socket failed for \"%s\": %s\n",
ctx->progname, optarg, strerror(errno));
CU(ERR_EXECUTION);
}
if ((rc = sa_addr_create(&ctx->saaAltio)) != SA_OK) {
fprintf(stderr, "%s:Error: Creating address failed for -b option (%d)\n",
ctx->progname, rc);
}
if (optarg[0] == '/') {
char *cpPath;
char *cpPerm;
int nPerm;
int n;
cpPath = strdup(optarg);
cpPerm = NULL;
nPerm = -1;
if ((cpPerm = strrchr(cpPath, ':')) != NULL) {
*cpPerm++ = '\0';
nPerm = 0;
for (i = 0; i < 4 && cpPerm[i] != '\0'; i++) {
if (!isdigit((int)cpPerm[i])) {
nPerm = -1;
break;
}
n = cpPerm[i] - '0';
if (n > 7) {
nPerm = -1;
break;
}
nPerm = ((nPerm << 3) | n);
}
if (nPerm == -1 || cpPerm[i] != '\0') {
fprintf(stderr, "%s:Error: Invalid permissions \"%s\"\n", ctx->progname, cpPerm);
CU(ERR_EXECUTION);
}
}
if ((rc = sa_addr_u2a(ctx->saaAltio, "unix:%s", cpPath)) != SA_OK) {
fprintf(stderr, "%s:Error: Parsing alternate IO guessing UNIX domain socket failed for \"%s\" (%d)\n",
ctx->progname, cpPath, rc);
CU(ERR_EXECUTION);
}
if ((rc = sa_bind(ctx->saAltio, ctx->saaAltio)) != SA_OK) {
fprintf(stderr, "%s:Error: Bind failed for \"%s\": %s\n",
ctx->progname, cpPath, strerror(errno));
CU(ERR_EXECUTION);
}
if (nPerm != -1) {
if (chmod(cpPath, nPerm) == -1) {
fprintf(stderr, "%s:Error: chmod failed for \"%s\": %s\n", ctx->progname, cpPath, strerror(errno));
CU(ERR_EXECUTION);
}
}
if (getuid() == 0 && getuid() != ctx->option_uid) {
if (chown(cpPath, ctx->option_uid, -1) == -1) {
fprintf(stderr, "%s:Error: chown failed for \"%s\": %s\n", ctx->progname, cpPath, strerror(errno));
CU(ERR_EXECUTION);
}
}
free(cpPath);
}
else {
if ((rc = sa_addr_u2a(ctx->saaAltio, "inet://%s", optarg)) != SA_OK) {
fprintf(stderr, "%s:Error: Parsing alternate IO guessing INET socket failed for \"%s\" (%d)\n",
ctx->progname, optarg, rc);
CU(ERR_EXECUTION);
}
if ((rc = sa_bind(ctx->saAltio, ctx->saaAltio)) != SA_OK) {
fprintf(stderr, "%s:Error: Bind failed for \"%s\": %s\n",
ctx->progname, optarg, strerror(errno));
CU(ERR_EXECUTION);
}
}
if ((rc = sa_listen(ctx->saAltio, -1)) != SA_OK) {
fprintf(stderr, "%s:Error: Listen to failed for \"%s\": %s\n",
ctx->progname, optarg, strerror(errno));
CU(ERR_EXECUTION);
}
}
break;
case 'c': /*POD [B<-c> I[I<:port>]] */
ctx->cpBindh = strdup(optarg);
if ((ctx->cpBindp = strrchr(ctx->cpBindh, ':')) != NULL) {
*ctx->cpBindp++ = NUL;
ctx->cpBindp = strdup(ctx->cpBindp);
}
else
ctx->cpBindp = strdup("0");
if ((rc = sa_addr_create(&ctx->saaBind)) != SA_OK) {
fprintf(stderr, "%s:Error: Creating address failed for -c option (%d)\n",
ctx->progname, rc);
}
if ((rc = sa_addr_u2a(ctx->saaBind, "inet://%s:%s", ctx->cpBindh, ctx->cpBindp)) != SA_OK) {
fprintf(stderr, "%s:Error: Parsing bind address failed for \"%s:%s\" (%d)\n",
ctx->progname, ctx->cpBindh, ctx->cpBindp, rc);
CU(ERR_EXECUTION);
}
break;
case 'd': /*POD [B<-d> I[I<:port>][,I[I<:port>], ...]] */
if (argz_create_sep(optarg, ',', &azHosts, &asHosts) != 0)
CU(ERR_EXECUTION);
cp = NULL;
while ((cp = argz_next(azHosts, asHosts, cp)) != NULL) {
if (ctx->nsc >= MAXNEWSSERVICES) {
fprintf(stderr, "%s:Error: Too many services (%d) using option -d\n", ctx->progname, ctx->nsc);
CU(ERR_EXECUTION);
}
cpHost = strdup(cp);
if ((cpPort = strrchr(cpHost, ':')) != NULL) {
*cpPort++ = NUL;
cpPort = strdup(cpPort);
}
else
cpPort = strdup("nntp");
ctx->ns[ctx->nsc].h = cpHost;
ctx->ns[ctx->nsc].p = cpPort;
if ((rc = sa_addr_create(&ctx->ns[ctx->nsc].saa)) != SA_OK) {
fprintf(stderr, "%s:Error: Creating address failed for -d option (%d)\n",
ctx->progname, rc);
}
if ((rc = sa_addr_u2a(ctx->ns[ctx->nsc].saa, "inet://%s:%s",
ctx->ns[ctx->nsc].h, ctx->ns[ctx->nsc].p)) != SA_OK) {
fprintf(stderr, "%s:Error: Parsing host address failed for \"%s:%s\" (%d)\n",
ctx->progname, ctx->ns[ctx->nsc].h, ctx->ns[ctx->nsc].p, rc);
CU(ERR_EXECUTION);
}
if ((rc = sa_create(&ctx->ns[ctx->nsc].sa)) != SA_OK) {
fprintf(stderr, "%s:Error: Creating TCP socket failed for \"%s:%s\": %s\n",
ctx->progname, ctx->ns[ctx->nsc].h, ctx->ns[ctx->nsc].p, strerror(errno));
CU(ERR_EXECUTION);
}
ctx->ns[ctx->nsc].nntp = NULL;
ctx->nsc++;
}
free(azHosts);
break;
case 'g': /*POD [B<-g> I] */
if (strcasecmp(optarg, "arg") == 0)
ctx->option_groupmode = GROUPMODE_ARG;
else if (strcasecmp(optarg, "envelope") == 0)
ctx->option_groupmode = GROUPMODE_ENVELOPE;
else if (strcasecmp(optarg, "header") == 0)
ctx->option_groupmode = GROUPMODE_HEADER;
else {
fprintf(stderr, "%s:Error: Invalid mode \"%s\" to option -g\n", ctx->progname, optarg);
CU(ERR_EXECUTION);
}
break;
case 'h': /*POD [B<-h> I:] */
cpHeadername = strdup(optarg);
if ((cp = strchr(cpHeadername, ':')) == NULL) {
free(cpHeadername);
fprintf(stderr, "%s:Error: header \"%s\" for -h option not terminated with colon\n",
ctx->progname, cpHeadername);
CU(ERR_EXECUTION);
}
cp++;
if (*cp == NUL) {
free(cpHeadername);
fprintf(stderr, "%s:Error: header \"%s\" for -h option has no value\n",
ctx->progname, cpHeadername);
CU(ERR_EXECUTION);
}
cpHeadervalue = strdup(cp);
*cp = NUL;
argz_add(&ctx->azHeaderValuePairs, &ctx->asHeaderValuePairs, cpHeadername);
argz_add(&ctx->azHeaderValuePairs, &ctx->asHeaderValuePairs, cpHeadervalue);
free(cpHeadervalue);
free(cpHeadername);
break;
case 'l': /*POD [B<-l> I[:I]] */
if ((cp = strrchr(optarg, ':')) != NULL) {
*cp++ = NUL;
if (*cp == NUL) {
fprintf(stderr, "%s:Error: empty logfile to option -l\n", ctx->progname);
CU(ERR_EXECUTION);
}
else
ctx->option_logfile = strdup(cp);
}
else
ctx->option_logfile = strdup("logfile");
if (l2_util_s2l(optarg, strlen(optarg), ',', &ctx->option_levelmask) != L2_OK) {
fprintf(stderr, "%s:Error: invalid level \"%s\" to option -l\n", ctx->progname, optarg);
CU(ERR_EXECUTION);
}
ctx->option_levelmask = L2_LEVEL_UPTO(ctx->option_levelmask);
break;
case 'm': /*POD [B<-m> I] */
ctx->option_mailfrom = strdup(optarg);
/* protect ourselfs from the substitution of backreferences.
* Missing varargs would cause segfaults. Rewrite capturing
* brackets to clustering syntax. Use poor man's s///g
* simulator as current str library doesn't support global
* substitution */
while (str_parse(ctx->option_mailfrom, "s/(.*?)\\((?!\\?:)(.*)/$1(?:$2/", &cp) > 0) {
free(ctx->option_mailfrom);
ctx->option_mailfrom = cp;
}
if (str_parse("<>", ctx->option_mailfrom) == -1) {
fprintf(stderr, "%s:Error: illegal regex \"%s\" to option -m.\n", ctx->progname, ctx->option_mailfrom);
CU(ERR_EXECUTION);
}
break;
case 'n': /*POD [B<-n> I] */
if (strlen(optarg) > sizeof(ctx->uname.nodename)-1) {
fprintf(stderr, "%s:Error: nodename \"%s\" to long to option -n.\n", ctx->progname, optarg);
CU(ERR_EXECUTION);
}
strcpy(ctx->uname.nodename, optarg);
break;
case 'o': /*POD [B<-o> I] */
if (strcasecmp(optarg, "post") == 0)
ctx->option_operationmode = OPERATIONMODE_POST;
else if (strcasecmp(optarg, "feed") == 0)
ctx->option_operationmode = OPERATIONMODE_FEED;
else {
if (strlen(optarg) != 9) {
fprintf(stderr, "%s:Error: Invalid format or length \"%s\" to option -o\n", ctx->progname, optarg);
CU(ERR_EXECUTION);
}
if (optarg[3] != '/') {
fprintf(stderr, "%s:Error: Invalid format or missing slash \"%s\" to option -o\n", ctx->progname, optarg);
CU(ERR_EXECUTION);
}
optarg[3] = NUL;
ctx->option_operationmodefakestatus = &optarg[0];
ctx->option_operationmodefakedsn = &optarg[4];
if ( strlen(ctx->option_operationmodefakestatus) != 3
|| !isdigit((int)ctx->option_operationmodefakestatus[0])
|| !isdigit((int)ctx->option_operationmodefakestatus[1])
|| !isdigit((int)ctx->option_operationmodefakestatus[2])) {
fprintf(stderr, "%s:Error: Invalid status in format \"%s\" to option -o\n", ctx->progname, optarg);
CU(ERR_EXECUTION);
}
if ( (strlen(ctx->option_operationmodefakedsn) != 5)
|| !isdigit((int)ctx->option_operationmodefakedsn[0])
|| (ctx->option_operationmodefakedsn[1] != '.')
|| !isdigit((int)ctx->option_operationmodefakedsn[2])
|| (ctx->option_operationmodefakedsn[3] != '.')
|| !isdigit((int)ctx->option_operationmodefakedsn[4])
|| (ctx->option_operationmodefakedsn[0] != ctx->option_operationmodefakestatus[0])) {
fprintf(stderr, "%s:Error: Invalid dsn in format \"%s\" to option -o\n", ctx->progname, optarg);
CU(ERR_EXECUTION);
}
}
break;
case 'r': /*POD [B<-r> I] */
ctx->option_restrictheader = strdup(optarg);
/* protect ourselfs from the substitution of backreferences.
* Missing varargs would cause segfaults. Rewrite capturing
* brackets to clustering syntax. Use poor man's s///g
* simulator as current str library doesn't support global
* substitution */
while (str_parse(ctx->option_restrictheader, "s/(.*?)\\((?!\\?:)(.*)/$1(?:$2/", &cp) > 0) {
free(ctx->option_restrictheader);
ctx->option_restrictheader = cp;
}
if (str_parse("<>", ctx->option_restrictheader) == -1) {
fprintf(stderr, "%s:Error: illegal regex \"%s\" to option -r.\n", ctx->progname, ctx->option_restrictheader);
CU(ERR_EXECUTION);
}
break;
case 's': /*POD [B<-s> I] */
ctx->option_maxmessagesize = atoi(optarg);
if(ctx->option_maxmessagesize < 64) {
fprintf(stderr, "%s:Error: maximum message size is unacceptable small.\n", ctx->progname);
CU(ERR_EXECUTION);
}
break;
case 't': /*POD [B<-t> I=I[,I=I[,...]] */
if (argz_create_sep(optarg, ',', &azTimeout, &asTimeout) != 0)
CU(ERR_EXECUTION);
cp = NULL;
while ((cp = argz_next(azTimeout, asTimeout, cp)) != NULL) {
cpName = strdup(cp);
if ((cpValue = strrchr(cpName, '=')) == NULL) {
fprintf(stderr, "%s:Error: comma-seperated argument %s to option -t have to be name=value.\n", ctx->progname, cp);
CU(ERR_EXECUTION);
}
*cpValue++ = NUL;
nValue = atoi(cpValue);
if (nValue < 0) {
fprintf(stderr, "%s:Error: timeout %s=%d to option -t must be a positive integer.\n", ctx->progname, cpName, nValue);
CU(ERR_EXECUTION);
}
if (strcmp(cpName, "lmtp") == 0) {
ctx->option_timeout_lmtp_accept = nValue;
ctx->option_timeout_lmtp_read = nValue;
ctx->option_timeout_lmtp_write = nValue;
}
else if (strcmp(cpName, "lmtp:accept") == 0)
ctx->option_timeout_lmtp_accept = nValue;
else if (strcmp(cpName, "lmtp:read") == 0)
ctx->option_timeout_lmtp_read = nValue;
else if (strcmp(cpName, "lmtp:write") == 0)
ctx->option_timeout_lmtp_write = nValue;
else if (strcmp(cpName, "nntp") == 0) {
ctx->option_timeout_nntp_connect = nValue;
ctx->option_timeout_nntp_read = nValue;
ctx->option_timeout_nntp_write = nValue;
}
else if (strcmp(cpName, "nntp:connect") == 0)
ctx->option_timeout_nntp_connect = nValue;
else if (strcmp(cpName, "nntp:read") == 0)
ctx->option_timeout_nntp_read = nValue;
else if (strcmp(cpName, "nntp:write") == 0)
ctx->option_timeout_nntp_write = nValue;
else {
fprintf(stderr, "%s:Error: unknown timeout %s to option -t.\n", ctx->progname, cpName);
CU(ERR_EXECUTION);
}
free(cpName);
}
free(azTimeout);
break;
case 'u': /*POD [B<-u> I] */
if (isdigit((int)optarg[0])) {
if ((sPasswd = getpwuid((uid_t)atoi(optarg))) == NULL) {
fprintf(stderr, "%s:Error: uid \"%s\" not found for -u option.\n", ctx->progname, optarg);
CU(ERR_EXECUTION);
}
}
else {
if ((sPasswd = getpwnam(optarg)) == NULL) {
fprintf(stderr, "%s:Error: loginname \"%s\" not found for -u option.\n", ctx->progname, optarg);
CU(ERR_EXECUTION);
}
}
ctx->option_uid = sPasswd->pw_uid;
break;
case 'v': /*POD [B<-v>] (version)*/
fprintf(stdout, "%s\n", lmtp2nntp_version.v_gnu);
CU(0);
break;
case '?':
default:
usage(ctx->progname);
CU(ERR_EXECUTION);
}
}
/*POD I [I ...] */
for (i = optind; i < argc; i++) {
argz_add(&ctx->azGroupargs, &ctx->asGroupargs, argv[i]);
}
/* if no positive ACL exists (option -a) add a wildcard match-all for IPv4 and IPv6 */
bOk = FALSE;
for (i = 0; i < ctx->option_aclc; i++) {
if (!ctx->option_acl[i].not) {
bOk = TRUE;
break;
}
}
if (!bOk) {
if (ctx->option_aclc >= MAXACLS) {
fprintf(stderr, "%s:Error: Too many ACL (%d) using option -a (no space for additional fake IPv4 ACL)\n", ctx->progname, ctx->option_aclc);
CU(ERR_EXECUTION);
}
ctx->option_acl[ctx->option_aclc].acl = "0.0.0.0";
ctx->option_acl[ctx->option_aclc].not = FALSE;
ctx->option_acl[ctx->option_aclc].prefixlen = 0;
if ((rc = sa_addr_create(&ctx->option_acl[ctx->option_aclc].saa)) != SA_OK) {
fprintf(stderr, "%s:Error: Creating fake address failed for -a option (%d)\n",
ctx->progname, rc);
}
if ((rc = sa_addr_u2a(ctx->option_acl[ctx->option_aclc].saa, "inet://%s:0", ctx->option_acl[ctx->option_aclc].acl)) != SA_OK) {
fprintf(stderr, "%s:Error: Parsing host address failed for \"%s:0\" (%s)\n",
ctx->progname, ctx->option_acl[ctx->option_aclc].acl,
sa_error(rc));
CU(ERR_EXECUTION);
}
ctx->option_aclc++;
}
if (!bOk) {
if (ctx->option_aclc >= MAXACLS) {
fprintf(stderr, "%s:Error: Too many ACL (%d) using option -a (no space for additional fake IPv6 ACL)\n", ctx->progname, ctx->option_aclc);
CU(ERR_EXECUTION);
}
ctx->option_acl[ctx->option_aclc].acl = "[::]";
ctx->option_acl[ctx->option_aclc].not = FALSE;
ctx->option_acl[ctx->option_aclc].prefixlen = 0;
if ((rc = sa_addr_create(&ctx->option_acl[ctx->option_aclc].saa)) != SA_OK) {
fprintf(stderr, "%s:Error: Creating fake address failed for -a option (%d)\n",
ctx->progname, rc);
}
if ((rc = sa_addr_u2a(ctx->option_acl[ctx->option_aclc].saa, "inet://%s:0", ctx->option_acl[ctx->option_aclc].acl)) != SA_OK) {
fprintf(stderr, "%s:Error: Parsing host address failed for \"%s:0\" (%s)\n",
ctx->progname, ctx->option_acl[ctx->option_aclc].acl,
sa_error(rc));
CU(ERR_EXECUTION);
}
ctx->option_aclc++;
}
if (getuid() != ctx->option_uid) {
if (setuid(ctx->option_uid) == -1) {
fprintf(stderr, "%s:Error: Setting UID to %d failed: %s\n",
ctx->progname, ctx->option_uid, strerror(errno));
CU(ERR_EXECUTION);
}
}
/* create L2 environment */
if (l2_env_create(&ctx->l2_env) != L2_OK) {
fprintf(stderr, "%s:Error: failed to create L2 environment\n", ctx->progname);
CU(ERR_EXECUTION);
}
/* register custom L2 formatters */
if (l2_env_formatter(ctx->l2_env, 'P', formatter_prefix, &ctx->ctx) != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to register prefix formatter\n", ctx->progname);
CU(ERR_EXECUTION);
}
if (l2_env_formatter(ctx->l2_env, 'D', l2_util_fmt_dump, NULL) != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to register dump formatter\n", ctx->progname);
CU(ERR_EXECUTION);
}
if (l2_env_formatter(ctx->l2_env, 'S', l2_util_fmt_string, NULL) != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to register string formatter\n", ctx->progname);
CU(ERR_EXECUTION);
}
if (l2_env_formatter(ctx->l2_env, 'm', formatter_errno, NULL) != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to register errno formatter\n", ctx->progname);
CU(ERR_EXECUTION);
}
/* create channel stream */
if (ctx->option_levelmask != L2_LEVEL_NONE && ctx->option_logfile != NULL) {
if (ctx->option_veryverbose)
rc = l2_spec(&ctx->l2, ctx->l2_env,
"prefix(prefix=\"%%b %%d %%H:%%M:%%S <%%L> lmtp2nntp[%%P]: \",timezone=local)"
" -> buffer(size=65536)"
" -> file(path=%s,append=1,perm=%d)",
ctx->option_logfile, 0644);
else
rc = l2_spec(&ctx->l2, ctx->l2_env,
"prefix(prefix=\"%%b %%d %%H:%%M:%%S <%%L> lmtp2nntp[%%P]: \",timezone=local)"
" -> file(path=%s,append=1,perm=%d)",
ctx->option_logfile, 0644);
if (rc != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to create stream\n", ctx->progname);
CU(ERR_EXECUTION);
}
if (l2_channel_levels(ctx->l2, ctx->option_levelmask, L2_LEVEL_NONE) != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to set global logging level\n", ctx->progname);
CU(ERR_EXECUTION);
}
if (l2_channel_open(ctx->l2) != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to open channel stream\n", ctx->progname);
CU(ERR_EXECUTION);
}
}
/* from this point on logging is up and running and fprintf(stderr, ...)
* should not be used in the remainder of the code
*/
log1(ctx, NOTICE, "startup, version %s", lmtp2nntp_version.v_gnu);
if (ctx->option_veryverbose)
log0(ctx, NOTICE, "logging very verbose (unbuffered)");
if ((ctx->option_pidfile != NULL) && ctx->option_killflag) {
if ((fd = fopen(ctx->option_pidfile, "r")) == NULL)
log1(ctx, ERROR, "cannot open pidfile \"%s\" for reading %m", ctx->option_pidfile);
else {
if (fscanf(fd, "%d\n", &pid) != 1) {
fclose(fd);
log1(ctx, ERROR, "cannot extract pid from pidfile \"%s\"", ctx->option_pidfile);
}
else {
fclose(fd);
log1(ctx, TRACE, "going to kill pid[%d]", pid);
if (kill(pid, SIGHUP) == -1)
log1(ctx, ERROR, "killing pid[%d] failed %m", pid);
if (unlink(ctx->option_pidfile) == -1)
log1(ctx, 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->saAltio == 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) {
log0(ctx, 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
* MAIL FROM:
* RCPT TO:
* DATA
* RSET
* NOOP
* QUIT
*/
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();
log1(ctx, NOTICE, "daemonized, previous pid[%d]", pid);
}
if (ctx->option_pidfile != NULL) {
if ((fd = fopen(ctx->option_pidfile, "w+")) == NULL)
log1(ctx, ERROR, "cannot open pidfile \"%s\" for writing %m", ctx->option_pidfile);
else {
fprintf(fd, "%d\n", getpid());
fclose(fd);
}
}
sa_timeout(ctx->saAltio, SA_TIMEOUT_ALL, 0, 0);
sa_timeout(ctx->saAltio, SA_TIMEOUT_ACCEPT, ctx->option_timeout_lmtp_accept, 0);
sa_timeout(ctx->saAltio, SA_TIMEOUT_READ, ctx->option_timeout_lmtp_read, 0);
sa_timeout(ctx->saAltio, SA_TIMEOUT_WRITE, ctx->option_timeout_lmtp_write, 0);
while (1) {
while (ctx->active_childs >= ctx->option_childsmax) {
log1(ctx, ERROR, "maximum number of childs (%d) reached - waiting (1s)", ctx->option_childsmax);
sleep(1);
}
if ((rc = sa_accept(ctx->saAltio, &ctx->saaIO, &ctx->saIO)) != SA_OK) {
if (rc == SA_ERR_SYS)
log3(ctx, ERROR, "accept failed: %s: (%d) %s", sa_error(rc), errno, strerror(errno));
else
log1(ctx, ERROR, "accept failed: %s", sa_error(rc));
sleep(10);
continue;
}
/* Access Control List */
bOk = FALSE;
/* check positive matches */
for (i = 0; i < ctx->option_aclc; i++) {
char *cpA1;
char *cpA2;
if (ctx->option_acl[i].not)
continue;
sa_addr_a2u(ctx->option_acl[i].saa, &cpA1);
sa_addr_a2u(ctx->saaIO, &cpA2);
if (sa_addr_match(ctx->saaIO, ctx->option_acl[i].saa, ctx->option_acl[i].prefixlen) == SA_OK) {
log4(ctx, TRACE, "positive/inclusive ACL \"%s\" (%s/%d) matches %s: YES (stop comparison)", ctx->option_acl[i].acl, cpA1, ctx->option_acl[i].prefixlen, cpA2);
bOk = TRUE;
break;
}
else
log4(ctx, TRACE, "positive/inclusive ACL \"%s\" (%s/%d) matches %s: NO", ctx->option_acl[i].acl, cpA1, ctx->option_acl[i].prefixlen, cpA2);
free(cpA1);
free(cpA2);
}
/* check negative matches */
for (i = 0; i < ctx->option_aclc; i++) {
char *cpA1;
char *cpA2;
if (!ctx->option_acl[i].not)
continue;
sa_addr_a2u(ctx->option_acl[i].saa, &cpA1);
sa_addr_a2u(ctx->saaIO, &cpA2);
if (sa_addr_match(ctx->saaIO, ctx->option_acl[i].saa, ctx->option_acl[i].prefixlen) == SA_OK) {
log4(ctx, TRACE, "negative/exclusive ACL \"%s\" (not %s/%d) matches %s: YES (stop comparison)", ctx->option_acl[i].acl, cpA1, ctx->option_acl[i].prefixlen, cpA2);
bOk = FALSE;
break;
}
else {
log4(ctx, TRACE, "negative/exclusive ACL \"%s\" (not %s/%d) matches %s: NO", ctx->option_acl[i].acl, cpA1, ctx->option_acl[i].prefixlen, cpA2);
}
}
if (bOk) {
char *cpA;
sa_addr_a2u(ctx->saaIO, &cpA);
log1(ctx, TRACE, "connection from %s accepted due to ACL", cpA);
free(cpA);
}
else {
char *cpA;
sa_addr_a2u(ctx->saaIO, &cpA);
log1(ctx, 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) {
log0(ctx, ERROR, "daemon cannot spawn child %m");
continue;
}
if (pid != 0) {
log1(ctx, INFO, "daemon forked process, new child pid[%d]", pid);
ctx->active_childs++;
sa_destroy(ctx->saIO);
sa_addr_destroy(ctx->saaIO);
continue;
}
log1(ctx, NOTICE, "startup new child process, parent pid[%d]", getppid());
/* child must close listening socket */
sa_destroy(ctx->saAltio);
ctx->saAltio = 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) {
log0(ctx, 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
* MAIL FROM:
* RCPT TO:
* DATA
* RSET
* NOOP
* QUIT
*/
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:
log0(ctx, NOTICE, "graceful shutdown shortly before exit - no more logging");
l2_channel_destroy(ctx->l2);
l2_env_destroy(ctx->l2_env);
if (ctx->saAltio)
sa_destroy(ctx->saAltio);
if (ctx->saaAltio)
sa_addr_destroy(ctx->saaAltio);
if (ctx->option_restrictheader != NULL)
free(ctx->option_restrictheader);
if (ctx->azHeaderValuePairs != NULL)
free(ctx->azHeaderValuePairs);
if (ctx->option_pidfile != NULL)
free(ctx->option_pidfile);
if (ctx->option_logfile != NULL)
free(ctx->option_logfile);
if (ctx->progname != NULL)
free(ctx->progname);
if (ctx->azGroupargs != NULL)
free(ctx->azGroupargs);
if (ctx != NULL)
free(ctx);
str_parse(NULL, NULL);
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
*/
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
nntp_rc_t rc;
lmtp_res_t res;
char str[STDSTRLEN];
int bOk;
int i;
nntp_io_t nntp_io;
log1(ctx, 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
*/
log0(ctx, 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
*/
log0(ctx, 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) {
log0(ctx, TRACE, "check if at least one NNTP service was successfully configured");
if (ctx->nsc == 0) {
res.statuscode = "451";
res.dsncode = "4.3.5";
res.statusmsg = "No valid NNTP services configured.";
CU(LMTP_OK);
}
}
log0(ctx, TRACE, "try to establish a session to any configured NNTP services");
if (ctx->option_operationmode == OPERATIONMODE_FAKE)
log0(ctx, NOTICE, "NNTP running in fake mode, network connections will be executed but result is ignored");
i = 0;
do {
log1(ctx, DEBUG, "trying ns[%d]", i);
bOk = TRUE;
log2(ctx, TRACE, "try %s:%s", ctx->ns[i].h, ctx->ns[i].p);
ctx->ns[i].l2 = ctx->l2;
if (bOk && (ctx->saaBind != NULL)) {
log2(ctx, DEBUG, "bind local socket to %s:%s", ctx->cpBindh, ctx->cpBindp);
if (sa_bind(ctx->ns[i].sa, ctx->saaBind) != SA_OK) {
bOk = FALSE;
log2(ctx, ERROR, "binding NNTP client to local address %s:%s failed, %m", ctx->cpBindh, ctx->cpBindp);
}
}
sa_timeout(ctx->ns[i].sa, SA_TIMEOUT_ALL, 0, 0);
sa_timeout(ctx->ns[i].sa, SA_TIMEOUT_CONNECT, ctx->option_timeout_nntp_connect, 0);
sa_timeout(ctx->ns[i].sa, SA_TIMEOUT_READ, ctx->option_timeout_nntp_read, 0);
sa_timeout(ctx->ns[i].sa, SA_TIMEOUT_WRITE, ctx->option_timeout_nntp_read, 0);
if (bOk) {
log0(ctx, DEBUG, "connect");
if (sa_connect(ctx->ns[i].sa, ctx->ns[i].saa) != SA_OK) {
bOk = FALSE;
log2(ctx, WARNING, "connect to %s:%s failed, %m",
ctx->ns[i].h, ctx->ns[i].p);
}
}
if (bOk) {
log0(ctx, DEBUG, "nntp_create");
nntp_io.ctx = &ctx->ns[i];
nntp_io.read = hook_nntp_read;
nntp_io.write = hook_nntp_write;
if ((ctx->ns[i].nntp = nntp_create(&nntp_io)) == NULL) {
bOk = FALSE;
log0(ctx, ERROR, "creation of NNTP context failed");
}
}
if (bOk) {
log0(ctx, DEBUG, "nntp_init");
if ((rc = nntp_init(ctx->ns[i].nntp)) != NNTP_OK) {
bOk = FALSE;
log2(ctx, ERROR, "initialization of NNTP context failed, (%d) %s", rc, nntp_error(rc));
}
}
if (bOk) {
log2(ctx, INFO, "NNTP session to %s:%s successfully established", ctx->ns[i].h, ctx->ns[i].p);
i++;
}
else {
log2(ctx, WARNING, "NNTP session establishment to %s:%s failed", ctx->ns[i].h, ctx->ns[i].p);
log1(ctx, DEBUG, "removing ns[%d] from list", i);
lmtp_gfs_ns(&ctx->ns[i]);
if (i < --ctx->nsc) {
memcpy(&ctx->ns[i], &ctx->ns[i+1], (ctx->nsc - i ) * sizeof(struct ns));
}
}
} while (i < ctx->nsc);
if (ctx->option_operationmode == OPERATIONMODE_FAKE)
log1(ctx, NOTICE, "NNTP running in fake mode, network connections successfully established=%d but ignored", ctx->nsc);
else
{
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 421 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
*/
log0(ctx, DEBUG, "check if at least one NNTP session successfully established");
if (ctx->nsc == 0) {
log0(ctx, 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->uname.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;
}
if (ns->p != NULL) {
free(ns->p);
ns->p = NULL;
}
if (ns->h != NULL) {
free(ns->h);
ns->h = NULL;
}
}
static void lmtp_gfs_lhlo(lmtp2nntp_t *ctx)
{
int i;
log0(ctx, TRACE, "LMTP service LHLO command - graceful shutdown");
for (i = 0; i < ctx->nsc; i++)
lmtp_gfs_ns(&ctx->ns[i]);
if (ctx->option_mailfrom != NULL)
free(ctx->option_mailfrom);
if (ctx->cpBindh != NULL)
free(ctx->cpBindh);
if (ctx->cpBindp != NULL)
free(ctx->cpBindp);
if (ctx->saBind != NULL)
sa_destroy(ctx->saBind);
if (ctx->saaBind != NULL)
sa_addr_destroy(ctx->saaBind);
}
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 according to RFC0821:
# ::= one, two, or three digits representing a decimal integer value in the range 0 through 255
# ::= any one of the 52 alphabetic characters A through Z in upper case and a through z in lower case
# ::= any one of the ten digits 0 through 9
# ::= | | "-"
# ::= |
# ::= |
# ::= "." "." "."
# ::= |
# ::=
# ::= | "#" | "[" "]"
# ::= | "."
#
# 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 according to RFC1035:
# ::= any one of the 52 alphabetic characters A through Z in upper case and a through z in lower case
# ::= any one of the ten digits 0 through 9
# ::= |
# ::= | "-"
# ::= |
#