*** /dev/null Sat Nov 23 01:38:07 2024
--- - Sat Nov 23 01:38:08 2024
***************
*** 0 ****
--- 1,2375 ----
+ /*
+ ** Copyright (c) 2001 The OSSP Project <http://www.ossp.org/>
+ ** Copyright (c) 2001 Cable & Wireless Deutschland <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/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.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 <pwd.h>
+
+ /* third party (included) */
+ #include "lmtp2nntp_argz.h"
+ #include "lmtp2nntp_shpat.h"
+ #include "lmtp2nntp_daemon.h"
+
+ /* third party (linked in) */
+ #include "str.h"
+ #include "l2.h"
+ #include "var.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(DMALLOC)
+ #include "dmalloc.h"
+ #endif
+ #include "lmtp2nntp_lmtp.h"
+ #include "lmtp2nntp_nntp.h"
+ #include "lmtp2nntp_msg.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 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 <lmtp2nntp.c -e 'while (<>) { 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<lmtp2nntp> */
+
+ /* use
+ * perl <lmtp2nntp.c -e 'while (<>) { 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<childsmax>] */
+ 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<pidfile>] */
+ ctx->option_pidfile = strdup(optarg);
+ break;
+ case 'V': /*POD [B<-V>] */
+ ctx->option_veryverbose = TRUE;
+ break;
+ case 'a': /*POD [B<-a> I<addr>/I<mask>[,I<addr>/I<mask>[,...]] */
+ 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<addr>[I<:port>]|C<->|I<path>[: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<addr>[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<addr>[I<:port>][,I<addr>[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<groupmode>] */
+ 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<header>:<value>] */
+ 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<level>[:I<logfile>]] */
+ 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<mailfrom>] */
+ 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<nodename>] */
+ 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<operationmode>] */
+ 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<restrictheader>] */
+ 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<size>] */
+ 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<name>=I<sec>[,I<name>=I<sec>[,...]] */
+ 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<uid>] */
+ 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<newsgroup> [I<newsgroup> ...] */
+ 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 <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();
+ 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 <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:
+ 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 <SP> <domain> <CRLF>
+ */
+ 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 <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
+ */
+ 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 <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;
+
+ log1(ctx, 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
+ */
+ log0(ctx, 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
+ */
+ log0(ctx, 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
+ */
+ log0(ctx, TRACE, "msg_create");
+ if ((ctx->msg = msg_create()) == 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
+ */
+ log0(ctx, 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
+ */
+ log0(ctx, 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
+ */
+ log0(ctx, TRACE, "checking if sender is allowed");
+ if (ctx->option_mailfrom != NULL) {
+ log2(ctx, 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;
+ char *group;
+
+ log1(ctx, 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
+ */
+ log0(ctx, 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
+ */
+ log0(ctx, 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
+ */
+ log0(ctx, 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
+ */
+ log1(ctx, DEBUG, "ctx->option_groupmode=%d", ctx->option_groupmode);
+ if (ctx->option_groupmode == GROUPMODE_ENVELOPE) {
+ log0(ctx, 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);
+ }
+ log1(ctx, 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);
+ }
+ log1(ctx, TRACE, "memorize group %s", group);
+ argz_add(&ctx->msg->azEnvgroups, &ctx->msg->asEnvgroups, group);
+ }
+ log1(ctx, 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:
+ 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;
+
+ log1(ctx, 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
+ */
+ log0(ctx, 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>
+ */
+ log0(ctx, 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);
+
+ log1(ctx, 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.
+ */
+ log0(ctx, 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
+ */
+ log0(ctx, 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
+ */
+ log0(ctx, 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
+ */
+ log0(ctx, 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
+ */
+ if ((ctx->msg = msg_create()) == 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, 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
+ */
+ if ((ctx->msg = msg_create()) == 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, 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
+ */
+ log0(ctx, 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) {
+ log2(ctx, TRACE, "\"%s\" matching against \"%s\" NO", errorstring, ctx->option_restrictheader);
+ }
+ else {
+ log2(ctx, TRACE, "\"%s\" matching against \"%s\": YES", errorstring, ctx->option_restrictheader);
+ bOk = TRUE;
+ break;
+ }
+ }
+ if (bOk) {
+ log0(ctx, 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;
+ }
+ }
+
+ /* Optionally add command line specified Header/Value Pairs
+ */
+ log0(ctx, TRACE, "adding header/value pairs");
+ if ((ctx->asHeaderValuePairs >= 1) && ((ctx->asHeaderValuePairs & 1) == 0)) {
+ cp = NULL;
+ while ((cp = argz_next(ctx->azHeaderValuePairs, ctx->asHeaderValuePairs, cp)) != NULL) {
+ var_rc_t var_rc;
+ char *res_ptr;
+ if ((var_rc = var_expand(cp, strlen(cp), &res_ptr, NULL,
+ ctx_lookup, ctx, &ctx_lookup_cfg, TRUE)) != VAR_OK) {
+ log2(ctx, ERROR, "expansion of '%s' failed: %s", cp, var_strerror(var_rc));
+ continue;
+ }
+ log1(ctx, DEBUG, "adding expanded header: \"%s\"", res_ptr);
+ argz_add(&ctx->msg->azHeaders, &ctx->msg->asHeaders, res_ptr);
+ free(res_ptr);
+ }
+ }
+
+ /* 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
+ */
+ log0(ctx, 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;
+ }
+
+ log0(ctx, TRACE, "deliver message");
+ bSuccess = NNTP_ERR_DELIVERY; /* assume a hard error for the worst case */
+ for (i = 0; i < ctx->nsc; i++) {
+ switch (ctx->option_operationmode) {
+ case OPERATIONMODE_FAKE:
+ ctx->ns[i].rc = NNTP_FAKE;
+ break;
+ case OPERATIONMODE_POST:
+ ctx->ns[i].rc = nntp_post(ctx->ns[i].nntp, ctx->msg);
+ break;
+ case OPERATIONMODE_FEED:
+ ctx->ns[i].rc = nntp_feed(ctx->ns[i].nntp, ctx->msg);
+ break;
+ }
+ if (ctx->ns[i].rc == NNTP_OK)
+ bSuccess = NNTP_OK;
+ if ( bSuccess != NNTP_OK
+ && (
+ (ctx->ns[i].rc == NNTP_ERR_SYSTEM)
+ || (ctx->ns[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;
+ log2(ctx, NOTICE, "%s %s", errorstring, "failed");
+ break;
+ case '4':
+ bSuccess = NNTP_DEFER;
+ log2(ctx, NOTICE, "%s %s", errorstring, "deferred");
+ break;
+ default:
+ bSuccess = NNTP_OK;
+ log2(ctx, 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)
+ log2(ctx, NOTICE, "%s %s", errorstring, "succeeded");
+ else if(bSuccess == NNTP_DEFER)
+ log2(ctx, WARNING, "%s %s", errorstring, "deferred");
+ else
+ log2(ctx, 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) {
+ 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->nsc; i++) {
+ if (ctx->ns[i].rc != NNTP_OK) {
+ str_format(errorstring, sizeof(errorstring),
+ "%s:%s returned %s\n"
+ "%s:%s lastresp \"%s\"",
+ ctx->ns[i].h, ctx->ns[i].p, nntp_error(ctx->ns[i].rc),
+ ctx->ns[i].h, ctx->ns[i].p, nntp_lastresp(ctx->ns[i].nntp));
+ 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;
+
+ log1(ctx, 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;
+
+ log1(ctx, 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)
+ {
+ log0(ctx, 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;
+
+ log1(ctx, 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)
+ {
+ log0(ctx, TRACE, "LMTP service QUIT command - graceful shutdown");
+
+ lmtp_gfs_rset(ctx);
+ resetsession(&ctx->session);
+ }
|