/* ** OSSP lmtp2nntp - Mail to News Gateway ** Copyright (c) 2001-2003 Ralf S. Engelschall ** Copyright (c) 2001-2003 The OSSP Project ** Copyright (c) 2001-2003 Cable & Wireless Germany ** ** This file is part of OSSP lmtp2nntp, an LMTP speaking local ** mailer which forwards mails as Usenet news articles via NNTP. ** It can be found at http://www.ossp.org/pkg/tool/lmtp2nntp/. ** ** This program is free software; you can redistribute it and/or ** modify it under the terms of the GNU General Public License ** as published by the Free Software Foundation; either version ** 2.0 of the License, or (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ** General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this file; if not, write to the Free Software ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 ** USA, or contact the OSSP project . ** ** lmtp2nntp_main.c: LMTP to NNTP main procedure */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* third party (included) */ #include "lmtp2nntp_argz.h" #include "lmtp2nntp_shpat.h" #include "lmtp2nntp_daemon.h" /* third party (linked in) */ #include "ex.h" #include "str.h" #include "l2.h" #include "sa.h" #include "var.h" #include "val.h" #include "popt.h" /* library version check (compile-time) */ #define L2_VERSION_HEX_REQ 0x001200 #define L2_VERSION_STR_REQ "0.1.0" #define STR_VERSION_HEX_REQ 0x009206 #define STR_VERSION_STR_REQ "0.9.6" #ifdef L2_VERSION_HEX #if L2_VERSION_HEX < L2_VERSION_HEX_REQ #error "require a newer version of OSSP L2" #endif #endif #ifdef STR_VERSION_HEX #if STR_VERSION_HEX < STR_VERSION_HEX_REQ #error "require a newer version of OSSP Str" #endif #endif /* own headers */ #include "lmtp2nntp_global.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif #if defined(HAVE_DMALLOC_H) && defined(WITH_DMALLOC) #include "dmalloc.h" #endif #include "lmtp2nntp_option.h" #include "lmtp2nntp_config.h" #include "lmtp2nntp_lmtp.h" #include "lmtp2nntp_nntp.h" #include "lmtp2nntp_msg.h" #include "lmtp2nntp_common.h" #include "sa.h" #define _LMTP2NNTP_VERSION_C_AS_HEADER_ #include "lmtp2nntp_version.c" #undef _LMTP2NNTP_VERSION_C_AS_HEADER_ #ifndef FALSE #define FALSE (1 != 1) #endif #ifndef TRUE #define TRUE (!FALSE) #endif #ifndef NUL #define NUL '\0' #endif #define STDSTRLEN 512 static lmtp_rc_t lmtp_cb_lhlo(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx); static lmtp_rc_t lmtp_cb_mail(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx); static lmtp_rc_t lmtp_cb_rcpt(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx); static lmtp_rc_t lmtp_cb_data(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx); static lmtp_rc_t lmtp_cb_noop(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx); static lmtp_rc_t lmtp_cb_rset(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx); static lmtp_rc_t lmtp_cb_quit(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx); static int helo_rfc0821domain(char *msg, char **domain); static int helo_rfc1035domain(char *msg, char **domain); static void catchsignal(int sig, ...); static void initsession(struct session *session); static void resetsession(struct session *session); int groupmatch(char *, size_t, char *); void logbook(l2_channel_t *ch, l2_level_t level, const char *fmt, ...) { va_list ap; l2_channel_t *ch2 = NULL; if (strchr(fmt, '$') == NULL) { if (l2_channel_downstream(ch, &ch2) != L2_OK) return; ch = ch2; } va_start(ap, fmt); l2_channel_vlog(ch, level, fmt, ap); va_end(ap); return; } static var_syntax_t syntax_ctx = { '\\', /* escape */ '$', /* varinit */ '{', /* startdelim */ '}', /* enddelim */ '[', /* startindex */ ']', /* endindex */ '#', /* current_index */ "a-zA-Z0-9.-" /* namechars */ }; static var_rc_t ctx_lookup( var_t *var, void *_ctx, const char *var_ptr, size_t var_len, int var_idx, const char **val_ptr, size_t *val_len, size_t *val_size) { lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx; var_rc_t rc; char *cp; optionval_t *ov; rc = VAR_ERR_UNDEFINED_VARIABLE; if (strncasecmp(var_ptr, "option.", strlen("option.")) == 0) { cp = str_dupex(var_ptr, var_len); if (val_get(ctx->val, cp, &ov) == VAL_OK) { if ((var_idx == 0) && (ov->ndata == 1) && (ov->type == OPT_SINGLE)) { /* request first/only single value */ *val_ptr = ov->data.s; *val_len = strlen(ov->data.s); *val_size = 0; rc = VAR_OK; } else if ((var_idx == 0) && (ov->ndata == 1) && (ov->type == OPT_FLAG)) { /* request first/only single value */ *val_ptr = ov->data.f == TRUE ? "yes" : "no"; *val_len = strlen(ov->data.s); *val_size = 0; rc = VAR_OK; } else if ((var_idx < ov->ndata) && (ov->type == OPT_MULTI)) { /* request second+ from multi value */ *val_ptr = ov->data.m[var_idx]; *val_len = strlen(ov->data.m[var_idx]); *val_size = 0; rc = VAR_OK; } } free(cp); } else if ( (strncasecmp(var_ptr, "msg.header.", strlen("msg.header.")) == 0) && (ctx->msg != NULL) ) { headerdata_t *hdI; int n; cp = str_dupex(var_ptr + strlen("msg.header."), var_len - strlen("msg.header.") + 1); n = strlen(cp); cp[n]= ':'; cp[n + 1] = NUL; for (hdI = ctx->msg->hdFirst; hdI != NULL; hdI = hdI->next) { /* for each header */ if (hdI->name == NULL || strlen(hdI->name) == 0) continue; if (strcasecmp(cp, hdI->name) == 0) break; } if (hdI != NULL) { if ((var_idx == 0) && (hdI->ndata == 1)) { /* request first/only single value */ *val_ptr = hdI->data.s; *val_len = strlen(hdI->data.s); *val_size = 0; rc = VAR_OK; } else if (var_idx < hdI->ndata) { /* request from multi value */ *val_ptr = hdI->data.m[var_idx]; *val_len = strlen(hdI->data.m[var_idx]); *val_size = 0; rc = VAR_OK; } } free(cp); } return rc; } static void lmtp_gfs_ns(struct ns *); static void lmtp_gfs_lhlo(lmtp2nntp_t *); static void lmtp_gfs_rset(lmtp2nntp_t *); static void lmtp_gfs_quit(lmtp2nntp_t *); static void sa2errno(sa_rc_t rv) { switch (rv) { case SA_OK: errno = 0; break; case SA_ERR_EOF: errno = 0; break; case SA_ERR_SYS: break; case SA_ERR_ARG: errno = EINVAL; break; case SA_ERR_USE: errno = EINVAL; break; case SA_ERR_MTC: errno = ENOENT; break; case SA_ERR_MEM: errno = ENOMEM; break; case SA_ERR_IMP: errno = ENOSYS; break; case SA_ERR_TMT: errno = EIO; break; case SA_ERR_NET: errno = EIO; break; case SA_ERR_FMT: errno = EIO; break; case SA_ERR_INT: errno = EIO; break; default: errno = EIO; } } static ssize_t hook_lmtp_read(void *_ctx, void *buf, size_t nbytes) { lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx; ssize_t rc; size_t n; sa_rc_t rv; if (ctx->saIO != NULL) { if ((rv = sa_read(ctx->saIO, buf, nbytes, &n)) != SA_OK) { if (rv == SA_ERR_SYS) logbook(ctx->l2, L2_LEVEL_DEBUG, "hook_lmtp_read() sa_read failed with \"%s\" (%d) %s", sa_error(rv), errno, strerror(errno)); else logbook(ctx->l2, L2_LEVEL_DEBUG, "hook_lmtp_read() sa_read failed with \"%s\"", sa_error(rv)); if (rv == SA_ERR_EOF) rc = 0; else rc = -1; sa2errno(rv); } else rc = (ssize_t)n; } else { rc = read(ctx->fdIOi, buf, nbytes); if (rc == -1) logbook(ctx->l2, L2_LEVEL_DEBUG, "hook_lmtp_read() read failed with (%d) %s", errno, strerror(errno)); } if (rc == -1) logbook(ctx->l2, L2_LEVEL_TRACE, "LMTP read error: %m"); else logbook(ctx->l2, L2_LEVEL_TRACE, "LMTP %5d << \"%{text}D\"", rc, buf, rc); logbook(ctx->l2, L2_LEVEL_DEBUG, "hook_lmtp_read() return, rc=%d", rc); return rc; } static ssize_t hook_lmtp_write(void *_ctx, const void *buf, size_t nbytes) { lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx; ssize_t rc; size_t n; sa_rc_t rv; logbook(ctx->l2, L2_LEVEL_TRACE, "LMTP %5d >> \"%{text}D\"", nbytes, buf, nbytes); if (ctx->saIO != NULL) { if ((rv = sa_write(ctx->saIO, buf, nbytes, &n)) != SA_OK) { if (rv == SA_ERR_SYS) logbook(ctx->l2, L2_LEVEL_DEBUG, "hook_lmtp_write() sa_write failed with \"%s\" (%d) %s", sa_error(rv), errno, strerror(errno)); else logbook(ctx->l2, L2_LEVEL_DEBUG, "hook_lmtp_write() sa_write failed with \"%s\"", sa_error(rv)); rc = -1; sa2errno(rv); } else rc = (ssize_t)n; } else { rc = write(ctx->fdIOo, buf, nbytes); if (rc == -1) logbook(ctx->l2, L2_LEVEL_DEBUG, "hook_lmtp_write() write failed with (%d) %s", errno, strerror(errno)); } if (rc == -1) logbook(ctx->l2, L2_LEVEL_TRACE, "LMTP write error"); return rc; } static ssize_t hook_nntp_read(void *_ctx, void *buf, size_t nbytes) { struct ns *ctx = (struct ns *)_ctx; ssize_t rc; size_t n; sa_rc_t rv; if ((rv = sa_read(ctx->sa, buf, nbytes, &n)) != SA_OK) { if (rv == SA_ERR_SYS) logbook(ctx->l2, L2_LEVEL_DEBUG, "hook_lmtp_read() sa_read failed with \"%s\" (%d) %s", sa_error(rv), errno, strerror(errno)); else logbook(ctx->l2, L2_LEVEL_DEBUG, "hook_lmtp_read() sa_read failed with \"%s\"", sa_error(rv)); if (rv == SA_ERR_EOF) rc = 0; else rc = -1; sa2errno(rv); } else rc = (ssize_t)n; if (rc == -1) logbook(ctx->l2, L2_LEVEL_TRACE, "NNTP read error: %m"); else logbook(ctx->l2, L2_LEVEL_TRACE, "NNTP %5d << \"%{text}D\"", rc, buf, rc); return rc; } static ssize_t hook_nntp_write(void *_ctx, const void *buf, size_t nbytes) { struct ns *ctx = (struct ns *)_ctx; ssize_t rc; size_t n; sa_rc_t rv; logbook(ctx->l2, L2_LEVEL_TRACE, "NNTP %5d >> \"%{text}D\"", nbytes, buf, nbytes); if ((rv = sa_write(ctx->sa, buf, nbytes, &n)) != SA_OK) { if (rv == SA_ERR_SYS) logbook(ctx->l2, L2_LEVEL_DEBUG, "hook_nntp_write() sa_write failed with \"%s\" (%d) %s", sa_error(rv), errno, strerror(errno)); else logbook(ctx->l2, L2_LEVEL_DEBUG, "hook_nntp_write() sa_write failed with \"%s\"", sa_error(rv)); rc = -1; sa2errno(rv); } else rc = (ssize_t)n; if (rc == -1) logbook(ctx->l2, L2_LEVEL_TRACE, "NNTP write error"); return rc; } static void catchsignal(int sig, ...) { va_list ap; static lmtp2nntp_t *ctx = NULL; pid_t pid; if(sig == 0) { va_start(ap, sig); if ((ctx = va_arg(ap, lmtp2nntp_t *)) == NULL) exit(ERR_EXECUTION); logbook(ctx->l2, L2_LEVEL_TRACE, "catching and logging signals now"); va_end(ap); return; } if (ctx != NULL) { switch (sig) { case SIGCHLD: logbook(ctx->l2, L2_LEVEL_NOTICE, "caught signal %d - wait for child", sig); pid = wait(NULL); ctx->active_childs--; logbook(ctx->l2, L2_LEVEL_NOTICE, "caught signal %d - child [%ld] terminated", sig, (long)pid); signal(sig, (void(*)())catchsignal); return; case SIGUSR1: logbook(ctx->l2, L2_LEVEL_NOTICE, "caught signal %d - flush logging stream", sig); l2_channel_flush(ctx->l2); signal(sig, (void(*)())catchsignal); return; case SIGHUP: case SIGINT: case SIGQUIT: logbook(ctx->l2, L2_LEVEL_NOTICE, "caught signal %d - exit - no more logging", sig); break; default: logbook(ctx->l2, L2_LEVEL_PANIC, "CAUGHT SIGNAL %d - EXIT - NO MORE LOGGING", sig); } l2_channel_destroy(ctx->l2); l2_env_destroy(ctx->l2_env); } exit(ERR_EXECUTION); } int main(int argc, char **argv) { int rc; lmtp_t *lmtp = NULL; lmtp_io_t lmtp_io; lmtp2nntp_t *ctx = NULL; int bOk; int i; /* general purpose scratch int, index ... */ pid_t pid; FILE *fd; lmtp2nntp_option_t *o; /* drop effective uid/gid privileges */ seteuid(getuid()); setegid(getgid()); /* use unbuffered stdio */ setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); /* library version check (run-time) */ if (l2_version.v_hex < L2_VERSION_HEX_REQ) { fprintf(stderr, "require OSSP L2 >= %s, found %s\n", L2_VERSION_STR_REQ, L2_VERSION_STR); CU(ERR_EXECUTION); } if (str_version.v_hex < STR_VERSION_HEX_REQ) { fprintf(stderr, "require OSSP Str >= %s, found %s\n", STR_VERSION_STR_REQ, STR_VERSION_STR); CU(ERR_EXECUTION); } /* create application context. fields are initialized to values that allow * detection of dynamic allocated resources which must be freed up for * graceful cleanup. These values are not necessarily useful application * defaults. Those defaults must be configured in lmtp2nntp_option.c * through option_register() calls */ if ((ctx = (lmtp2nntp_t *)malloc(sizeof(lmtp2nntp_t))) == NULL) CU(ERR_EXECUTION); ctx->ctx.vp = ctx; ctx->prival = NULL; ctx->val = NULL; ctx->progname = NULL; ctx->option_groupmode = GROUPMODE_UNDEF; ctx->option_operationmode = OPERATIONMODE_UNDEF; ctx->option_operationmodefakestatus = NULL; ctx->option_operationmodefakedsn = NULL; /* is joined to fakestatus in a single malloc(3) */ ctx->option_maxmessagesize = 0; ctx->option_firstheaderrule = NULL; ctx->option_timeout_lmtp_accept = 0; ctx->option_timeout_lmtp_read = 0; ctx->option_timeout_lmtp_write = 0; ctx->option_timeout_nntp_connect = 0; ctx->option_timeout_nntp_read = 0; ctx->option_timeout_nntp_write = 0; ctx->option_nodename = NULL; ctx->option_mailfrom = NULL; ctx->option_restrictheader = NULL; ctx->option_pidfile = NULL; ctx->option_killflag = FALSE; ctx->option_uid = getuid(); ctx->option_daemon = FALSE; ctx->nacl = 0; ctx->pacl = NULL; ctx->option_childsmax = 0; ctx->active_childs = 0; ctx->l2_env = NULL; ctx->l2 = NULL; ctx->saaServerbind = NULL; ctx->saServerbind = NULL; ctx->saaClientbind = NULL; ctx->saClientbind = NULL; ctx->saaIO = NULL; ctx->saIO = NULL; ctx->fdIOi = -1; ctx->fdIOo = -1; ctx->nns = 0; ctx->pns = NULL; ctx->azGroupargs = NULL; ctx->asGroupargs = 0; initsession(&ctx->session); ctx->msg = NULL; ctx->config_varregex = NULL; ctx->config_varctx = NULL; ctx->msgcount = 0; /* private application context */ if (val_create(&ctx->prival) != VAL_OK) CU(ERR_EXECUTION); /* create printable variables context and mount it into the private application context */ if (val_create(&ctx->val) != VAL_OK) CU(ERR_EXECUTION); if (val_reg(ctx->prival, "printable", VAL_TYPE_VAL, "printable variables", NULL) != VAL_OK) CU(ERR_EXECUTION); if (val_set(ctx->prival, "printable", ctx->val) != VAL_OK) CU(ERR_EXECUTION); /* feed lib_val */ if (val_reg(ctx->prival, "msgcount", VAL_TYPE_INT, "number of messages processed so far", (void *)&ctx->msgcount) != VAL_OK) CU(ERR_EXECUTION); if (val_reg(ctx->prival, "nodename", VAL_TYPE_PTR, "nodename configured or uname(3)", (void *)&ctx->option_nodename) != VAL_OK) CU(ERR_EXECUTION); /* set progname */ ctx->progname = strdup(argv[0]); /* establish variable expansion context */ { char *cp; if ((rc = var_create(&ctx->config_varctx)) != VAR_OK) { fprintf(stderr, "%s:Error: create varctx context failed with %s (%d)", ctx->progname, var_strerror(ctx->config_varctx, rc, &cp) == VAR_OK ? cp : "Unknown Error", rc); CU(ERR_EXECUTION); } if ((rc = var_config(ctx->config_varctx, VAR_CONFIG_SYNTAX, &syntax_ctx)) != VAR_OK) { fprintf(stderr, "%s:Error: config varctx context failed with %s (%d)", ctx->progname, var_strerror(ctx->config_varctx, rc, &cp) == VAR_OK ? cp : "Unknown Error", rc); CU(ERR_EXECUTION); } if ((rc = var_config(ctx->config_varctx, VAR_CONFIG_CB_VALUE, ctx_lookup, ctx)) != VAR_OK) { fprintf(stderr, "%s:Error: config varctx callback failed with %s (%d)", ctx->progname, var_strerror(ctx->config_varctx, rc, &cp) == VAR_OK ? cp : "Unknown Error", rc); CU(ERR_EXECUTION); } } /* read in the arguments */ { lmtp2nntp_option_rc_t rvo; lmtp2nntp_config_rc_t rvc; ex_t ex; if (option_create(&o, ctx->val) != OPTION_OK) CU(ERR_EXECUTION); rvo = option_parse(o, argc, argv); rvc = CONFIG_OK; try { rvc = config_context(ctx); } catch(ex) { rvc = CONFIG_ERR_TRY; } if (rvc == CONFIG_OK_DRY) CU(0); if (rvo != OPTION_OK || rvc != CONFIG_OK) CU(ERR_EXECUTION); } if (getuid() != ctx->option_uid) { if (setuid(ctx->option_uid) == -1) { fprintf(stderr, "%s:Error: Setting UID to %d failed: %s\n", ctx->progname, (int)ctx->option_uid, strerror(errno)); CU(ERR_EXECUTION); } } if ((ctx->option_pidfile != NULL) && ctx->option_killflag) { if ((fd = fopen(ctx->option_pidfile, "r")) == NULL) logbook(ctx->l2, L2_LEVEL_ERROR, "cannot open pidfile \"%s\" for reading %m", ctx->option_pidfile); else { if (fscanf(fd, "%d\n", (int *)&pid) != 1) { fclose(fd); logbook(ctx->l2, L2_LEVEL_ERROR, "cannot extract pid from pidfile \"%s\"", ctx->option_pidfile); } else { fclose(fd); logbook(ctx->l2, L2_LEVEL_TRACE, "going to kill pid[%d]", pid); if (kill(pid, SIGHUP) == -1) logbook(ctx->l2, L2_LEVEL_ERROR, "killing pid[%d] failed %m", pid); if (unlink(ctx->option_pidfile) == -1) logbook(ctx->l2, L2_LEVEL_ERROR, "unlinking pidfile \"%s\" failed %m", ctx->option_pidfile); } } CU(0); } catchsignal(0, ctx); signal(SIGCHLD, (void(*)())catchsignal); signal(SIGHUP, (void(*)())catchsignal); signal(SIGINT, (void(*)())catchsignal); signal(SIGQUIT, (void(*)())catchsignal); signal(SIGILL, (void(*)())catchsignal); signal(SIGBUS, (void(*)())catchsignal); signal(SIGSEGV, (void(*)())catchsignal); signal(SIGSYS, (void(*)())catchsignal); signal(SIGTERM, (void(*)())catchsignal); signal(SIGUSR1, (void(*)())catchsignal); signal(SIGUSR2, SIG_IGN ); /* loop for LMTP protocol with support for alternate io through daemon */ if (ctx->saServerbind == NULL) { /* initialize LMTP context */ ctx->fdIOi = STDIN_FILENO; ctx->fdIOo = STDOUT_FILENO; lmtp_io.ctx = ctx; lmtp_io.read = hook_lmtp_read; lmtp_io.write = hook_lmtp_write; if ((lmtp = lmtp_create(&lmtp_io)) == NULL) { logbook(ctx->l2, L2_LEVEL_ERROR, "Unable to initialize LMTP library\n"); CU(ERR_EXECUTION); } /* RFC0821, 4.5.1. MINIMUM IMPLEMENTATION * In order to make SMTP workable, the following minimum implementation * is required for all receivers: [...] * RFC0821, 4.1.2. COMMAND SYNTAX * * Verb Parameter * ----+------------------------------- * HELO * 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(); logbook(ctx->l2, L2_LEVEL_NOTICE, "daemonized, previous pid[%d]", pid); } if (ctx->option_pidfile != NULL) { if ((fd = fopen(ctx->option_pidfile, "w+")) == NULL) logbook(ctx->l2, L2_LEVEL_ERROR, "cannot open pidfile \"%s\" for writing %m", ctx->option_pidfile); else { fprintf(fd, "%d\n", (int)getpid()); fclose(fd); } } sa_timeout(ctx->saServerbind, SA_TIMEOUT_ALL, 0, 0); sa_timeout(ctx->saServerbind, SA_TIMEOUT_ACCEPT, ctx->option_timeout_lmtp_accept, 0); sa_timeout(ctx->saServerbind, SA_TIMEOUT_READ, ctx->option_timeout_lmtp_read, 0); sa_timeout(ctx->saServerbind, SA_TIMEOUT_WRITE, ctx->option_timeout_lmtp_write, 0); while (1) { while (ctx->active_childs >= ctx->option_childsmax) { logbook(ctx->l2, L2_LEVEL_ERROR, "maximum number of childs (%d) reached - waiting (1s)", ctx->option_childsmax); sleep(1); } if ((rc = sa_accept(ctx->saServerbind, &ctx->saaIO, &ctx->saIO)) != SA_OK) { if (rc == SA_ERR_SYS) logbook(ctx->l2, L2_LEVEL_ERROR, "accept failed: %s: (%d) %s", sa_error(rc), errno, strerror(errno)); else logbook(ctx->l2, L2_LEVEL_ERROR, "accept failed: %s", sa_error(rc)); sleep(10); continue; } /* Access Control List */ bOk = FALSE; /* check positive matches */ for (i = 0; i < ctx->nacl; i++) { char *cpA1; char *cpA2; if (ctx->pacl[i].not) continue; sa_addr_a2u(ctx->pacl[i].saa, &cpA1); sa_addr_a2u(ctx->saaIO, &cpA2); if (sa_addr_match(ctx->saaIO, ctx->pacl[i].saa, ctx->pacl[i].prefixlen) == SA_OK) { logbook(ctx->l2, L2_LEVEL_TRACE, "positive/inclusive ACL \"%s\" (%s/%d) matches %s: YES (stop comparison)", ctx->pacl[i].acl, cpA1, ctx->pacl[i].prefixlen, cpA2); bOk = TRUE; break; } else logbook(ctx->l2, L2_LEVEL_TRACE, "positive/inclusive ACL \"%s\" (%s/%d) matches %s: NO", ctx->pacl[i].acl, cpA1, ctx->pacl[i].prefixlen, cpA2); free(cpA1); free(cpA2); } /* check negative matches */ for (i = 0; i < ctx->nacl; i++) { char *cpA1; char *cpA2; if (!ctx->pacl[i].not) continue; sa_addr_a2u(ctx->pacl[i].saa, &cpA1); sa_addr_a2u(ctx->saaIO, &cpA2); if (sa_addr_match(ctx->saaIO, ctx->pacl[i].saa, ctx->pacl[i].prefixlen) == SA_OK) { logbook(ctx->l2, L2_LEVEL_TRACE, "negative/exclusive ACL \"%s\" (not %s/%d) matches %s: YES (stop comparison)", ctx->pacl[i].acl, cpA1, ctx->pacl[i].prefixlen, cpA2); bOk = FALSE; break; } else { logbook(ctx->l2, L2_LEVEL_TRACE, "negative/exclusive ACL \"%s\" (not %s/%d) matches %s: NO", ctx->pacl[i].acl, cpA1, ctx->pacl[i].prefixlen, cpA2); } } if (bOk) { char *cpA; sa_addr_a2u(ctx->saaIO, &cpA); logbook(ctx->l2, L2_LEVEL_TRACE, "connection from %s accepted due to ACL", cpA); free(cpA); } else { char *cpA; sa_addr_a2u(ctx->saaIO, &cpA); logbook(ctx->l2, L2_LEVEL_ERROR, "connection from %s refused due to ACL", cpA); free(cpA); sa_destroy(ctx->saIO); sa_addr_destroy(ctx->saaIO); continue; } /* logging buffer must be empty before fork otherwise content is * duplicated and written twice on next flush */ l2_channel_flush(ctx->l2); pid = fork(); if (pid == -1) { logbook(ctx->l2, L2_LEVEL_ERROR, "daemon cannot spawn child %m"); continue; } if (pid != 0) { logbook(ctx->l2, L2_LEVEL_INFO, "daemon forked process, new child pid[%d]", pid); ctx->active_childs++; sa_destroy(ctx->saIO); sa_addr_destroy(ctx->saaIO); continue; } logbook(ctx->l2, L2_LEVEL_NOTICE, "startup new child process, parent pid[%d]", getppid()); /* child must close listening socket */ sa_destroy(ctx->saServerbind); ctx->saServerbind = NULL; /* prevent cleanup from free'ing this again */ /* initialize LMTP context */ lmtp_io.ctx = ctx; lmtp_io.read = hook_lmtp_read; lmtp_io.write = hook_lmtp_write; if ((lmtp = lmtp_create(&lmtp_io)) == NULL) { logbook(ctx->l2, L2_LEVEL_ERROR, "Unable to initialize LMTP library\n"); CU(ERR_EXECUTION); } /* RFC0821, 4.5.1. MINIMUM IMPLEMENTATION * In order to make SMTP workable, the following minimum implementation * is required for all receivers: [...] * RFC0821, 4.1.2. COMMAND SYNTAX * * Verb Parameter * ----+------------------------------- * HELO * 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: signal(SIGCHLD, SIG_DFL); signal(SIGHUP, SIG_DFL); signal(SIGINT, SIG_DFL); signal(SIGQUIT, SIG_DFL); signal(SIGILL, SIG_DFL); signal(SIGBUS, SIG_DFL); signal(SIGSEGV, SIG_DFL); signal(SIGSYS, SIG_DFL); signal(SIGTERM, SIG_DFL); signal(SIGUSR1, SIG_DFL); signal(SIGUSR2, SIG_DFL); logbook(ctx->l2, L2_LEVEL_NOTICE, "graceful shutdown shortly before exit - no more logging"); if (ctx->l2 != NULL) l2_channel_destroy(ctx->l2); if (ctx->l2_env != NULL) l2_env_destroy(ctx->l2_env); if (ctx->saServerbind) sa_destroy(ctx->saServerbind); if (ctx->saaServerbind) sa_addr_destroy(ctx->saaServerbind); if (ctx->option_restrictheader != NULL) free(ctx->option_restrictheader); if (ctx->option_pidfile != NULL) free(ctx->option_pidfile); if (ctx->pns != NULL) free(ctx->pns); if (ctx->pacl != NULL) { for (i = 0; i < ctx->nacl; i++) if (ctx->pacl[i].saa != NULL) sa_addr_destroy(ctx->pacl[i].saa); free(ctx->pacl); } if (ctx->option_nodename != NULL) free(ctx->option_nodename); if (ctx->prival != NULL) val_destroy(ctx->prival); if (ctx->val != NULL) val_destroy(ctx->val); if (ctx->progname != NULL) free(ctx->progname); if (ctx->azGroupargs != NULL) free(ctx->azGroupargs); if (ctx->config_varregex != NULL) var_destroy(ctx->config_varregex); if (ctx->config_varctx != NULL) var_destroy(ctx->config_varctx); if (ctx->option_operationmodefakestatus != NULL) free(ctx->option_operationmodefakestatus); /* includes dsn */ if (ctx != NULL) free(ctx); str_parse(NULL, NULL); if (o != NULL) (void)option_destroy(o); return rc; } static void resetsession(struct session *session) { if (session->lhlo_domain != NULL) free(session->lhlo_domain); initsession(session); return; } static void initsession(struct session *session) { session->lhlo_seen = FALSE; session->lhlo_domain = NULL; return; } static lmtp_rc_t lmtp_cb_lhlo(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx) { /* * RFC0821 [excerpt] 4.1. SMTP COMMANDS * 4.1.1. COMMAND SEMANTICS, HELO * This command and an OK reply to it confirm that both the sender-SMTP * and the receiver-SMTP are in the initial state, that is, there is no * transaction in progress and all state tables and buffers are cleared. * * The first command in a session must be the HELO command. The HELO * command may be used later in a session as well. If the HELO command * argument is not acceptable a 501 failure reply must be returned and * the receiver-SMTP must stay in the same state. * * If the transaction beginning command argument is not acceptable a 501 * failure reply must be returned and the receiver-SMTP must stay in the * same state. If the commands in a transaction are out of order a 503 * failure reply must be returned and the receiver-SMTP must stay in the * same state. * * HELO */ lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx; nntp_rc_t rc; lmtp_res_t res; char str[STDSTRLEN]; int bOk; int i; int is; nntp_io_t nntp_io; logbook(ctx->l2, L2_LEVEL_INFO, "LMTP service executing LHLO command < %s", req->msg); /* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 503 Bad sequence of commands * RFC1893 2. Status Codes 5.X.X Permanent Failure * RFC1893 3.5 Network and Routing Status X.0.0 Other undefined Status */ logbook(ctx->l2, L2_LEVEL_TRACE, "checking for duplicate LHLO"); if (ctx->session.lhlo_seen) { res.statuscode = "503"; res.dsncode = "5.0.0"; res.statusmsg = "Duplicate LHLO."; CU(LMTP_OK); } /* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 501 Syntax error in parameters or arguments * RFC1893 2. Status Codes 5.X.X Permanent Failure * RFC1893 3.5 Network and Routing Status X.0.0 Other undefined Status */ logbook(ctx->l2, L2_LEVEL_TRACE, "checking domain to match either RFC0821 or RFC1035 syntax"); if (! ( helo_rfc0821domain(req->msg, &ctx->session.lhlo_domain) > 0 || helo_rfc1035domain(req->msg, &ctx->session.lhlo_domain) > 0)) { res.statuscode = "501"; res.dsncode = "5.0.0"; res.statusmsg = "Please identify yourself. Domain must match RFC0821/RFC1035."; CU(LMTP_OK); } /* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 451 Requested action aborted: local error in processing * RFC1893 2. Status Codes 4.X.X Persistent Transient Failure * RFC1893 3.5 Network and Routing Status X.3.5 System incorrectly configured */ if (ctx->option_operationmode != OPERATIONMODE_FAKE) { logbook(ctx->l2, L2_LEVEL_TRACE, "check if at least one NNTP service was successfully configured"); if (ctx->nns == 0) { res.statuscode = "451"; res.dsncode = "4.3.5"; res.statusmsg = "No valid NNTP services configured."; CU(LMTP_OK); } } logbook(ctx->l2, L2_LEVEL_TRACE, "try to establish a session to any configured NNTP services"); if (ctx->option_operationmode == OPERATIONMODE_FAKE) logbook(ctx->l2, L2_LEVEL_NOTICE, "NNTP running in fake mode, network connections will be executed but result is ignored"); i = 0; is = 0; while (i < ctx->nns) { logbook(ctx->l2, L2_LEVEL_DEBUG, "trying ns[%d]", i); bOk = TRUE; logbook(ctx->l2, L2_LEVEL_TRACE, "try destination#%d ${option.destination[%d]}", i, i); ctx->pns[i].l2 = ctx->l2; if (bOk && (ctx->saaClientbind != NULL)) { logbook(ctx->l2, L2_LEVEL_DEBUG, "bind local socket to ${option.client}"); if (sa_bind(ctx->pns[i].sa, ctx->saaClientbind) != SA_OK) { bOk = FALSE; logbook(ctx->l2, L2_LEVEL_ERROR, "binding NNTP client to local address ${option.client} failed, %m"); } } sa_timeout(ctx->pns[i].sa, SA_TIMEOUT_ALL, 0, 0); sa_timeout(ctx->pns[i].sa, SA_TIMEOUT_CONNECT, ctx->option_timeout_nntp_connect, 0); sa_timeout(ctx->pns[i].sa, SA_TIMEOUT_READ, ctx->option_timeout_nntp_read, 0); sa_timeout(ctx->pns[i].sa, SA_TIMEOUT_WRITE, ctx->option_timeout_nntp_read, 0); if (bOk) { logbook(ctx->l2, L2_LEVEL_DEBUG, "connect"); if (sa_connect(ctx->pns[i].sa, ctx->pns[i].saa) != SA_OK) { bOk = FALSE; logbook(ctx->l2, L2_LEVEL_WARNING, "connect to destination#%d ${option.destination[%d]} failed, %m", i, i); } } if (bOk) { logbook(ctx->l2, L2_LEVEL_DEBUG, "nntp_create"); nntp_io.ctx = &ctx->pns[i]; nntp_io.read = hook_nntp_read; nntp_io.write = hook_nntp_write; if ((ctx->pns[i].nntp = nntp_create(&nntp_io)) == NULL) { bOk = FALSE; logbook(ctx->l2, L2_LEVEL_ERROR, "creation of NNTP context failed"); } } if (bOk) { logbook(ctx->l2, L2_LEVEL_DEBUG, "nntp_init"); if ((rc = nntp_init(ctx->pns[i].nntp)) != NNTP_OK) { bOk = FALSE; logbook(ctx->l2, L2_LEVEL_ERROR, "initialization of NNTP context failed, (%d) %s", rc, nntp_error(rc)); } } if (bOk) { logbook(ctx->l2, L2_LEVEL_INFO, "NNTP session to destination#%d ${option.destination[%d]} successfully established", i, i); is++; } else { logbook(ctx->l2, L2_LEVEL_WARNING, "NNTP session establishment to destination#%d ${option.destination[%d]} failed", i, i); lmtp_gfs_ns(&ctx->pns[i]); } i++; } logbook(ctx->l2, L2_LEVEL_INFO, "NNTP network connections tried %d, successful %d", i, is); if (ctx->option_operationmode == OPERATIONMODE_FAKE) logbook(ctx->l2, L2_LEVEL_NOTICE, "NNTP running in fake mode, ignoring status of real network connections"); else { /* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 421 Service not available * RFC1893 2. Status Codes 4.X.X Persistent Transient Failure * RFC1893 3.5 Network and Routing Status X.4.1 No answer from host */ logbook(ctx->l2, L2_LEVEL_DEBUG, "check if at least one NNTP session successfully established"); if (is == 0) { logbook(ctx->l2, L2_LEVEL_ERROR, "no NNTP session established"); res.statuscode = "421"; res.dsncode = "4.4.1"; res.statusmsg = "No NNTP session established."; CU(LMTP_OK); } } ctx->session.lhlo_seen = TRUE; /* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 250 Requested mail action okay, completed */ str_format(str, sizeof(str), "%s Hello %s, pleased to meet you.\n" /* RFC2821 4.1.1.1 */ "ENHANCEDSTATUSCODES\n" /* RFC2034 */ "DSN\n" /* RFC1894 */ "PIPELINING\n" /* RFC1854 */ "8BITMIME", /* RFC1652 */ ctx->option_nodename, ctx->session.lhlo_domain); res.statuscode = "250"; res.dsncode = NULL; /* DSN not used for greeting */ res.statusmsg = str; CU(LMTP_OK); CUS: lmtp_response(lmtp, &res); return rc; } static void lmtp_gfs_ns(struct ns *ns) { if (ns->nntp != NULL) { nntp_destroy(ns->nntp); ns->nntp = NULL; } if (ns->sa != NULL) { sa_destroy(ns->sa); ns->sa = NULL; } if (ns->saa != NULL) { sa_addr_destroy(ns->saa); ns->saa = NULL; } } static void lmtp_gfs_lhlo(lmtp2nntp_t *ctx) { int i; logbook(ctx->l2, L2_LEVEL_TRACE, "LMTP service LHLO command - graceful shutdown"); for (i = 0; i < ctx->nns; i++) lmtp_gfs_ns(&ctx->pns[i]); if (ctx->option_mailfrom != NULL) free(ctx->option_mailfrom); if (ctx->saClientbind != NULL) sa_destroy(ctx->saClientbind); if (ctx->saaClientbind != NULL) sa_addr_destroy(ctx->saaClientbind); } static int helo_rfc0821domain(char *msg, char **domain) { int rc; rc = str_parse(msg, "^.+ (" /* ## ## The mega Perl regular expression below is generated ## with the following Perl program. This is only possible ## because the given grammar is Chomsky-3 (right or left ## linear grammar, but noth both). ## # BNF grammar for 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 # ::= | # ::= | "-" # ::= | #