/* ** 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 # ::= | # ::= | "-" # ::= | #