/* ** 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 /* third party */ #include "str.h" #include "argz.h" #include "shpat_match.h" #include "l2.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 0x009205 #define STR_VERSION_STR_REQ "0.9.5" #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 128 #define MAXNEWSSERVICES 16 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 int connect_nonb(int, const struct sockaddr *, socklen_t, int); 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_t *sa; int s; /* socket */ nntp_t *nntp; nntp_rc_t rc; }; typedef struct { char *progname; char *option_logfile; int option_groupmode; int option_deliverymode; char *option_deliverymodefakestatus; char *option_deliverymodefakedsn; int option_maxmessagesize; int option_waittime; char *option_mailfrom; unsigned int option_levelmask; l2_stream_t *l2; char *cpBindh; char *cpBindp; sa_t *saBind; int nsc; struct ns ns[MAXNEWSSERVICES]; char *azGroupargs; size_t asGroupargs; struct session session; msg_t *msg; struct utsname uname; } lmtp2nntp_t; static void lmtp_gfs_lhlo(lmtp2nntp_t *ctx); static void lmtp_gfs_rset(lmtp2nntp_t *ctx); static void lmtp_gfs_quit(lmtp2nntp_t *ctx); enum { GROUPMODE_ARG, GROUPMODE_ENVELOPE, GROUPMODE_HEADER }; enum { DELIVERYMODE_FAKE, DELIVERYMODE_POST, DELIVERYMODE_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 " "lmtp2nntp " "[-b bindaddr[:port]] " "[-d deliverymode] " "[-g groupmode] " "[-h host[:port][,host[:port], ...]] " "[-m mailfrom] " "[-n nodename] " "[-s size] " "[-l level[:logfile]] " "[-v] " "[-w waittime] " "newsgroup [newsgroup ...] " "\n", command); return; } static ssize_t trace_lmtp_read(void *_ctx, int d, void *buf, size_t nbytes) { lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx; ssize_t rc; rc = read(d, buf, nbytes); if (rc == -1) log0(ctx, TRACE, "LMTP read error: %m"); else log3(ctx, TRACE, "LMTP %5d << \"%{text}D\"", rc, buf, rc); return rc; } static ssize_t trace_lmtp_write(void *_ctx, int d, const void *buf, size_t nbytes) { lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx; ssize_t rc; log3(ctx, TRACE, "LMTP %5d >> \"%{text}D\"", nbytes, buf, nbytes); rc = write(d, buf, nbytes); if (rc == -1) log0(ctx, TRACE, "LMTP write error: %m"); return rc; } static ssize_t trace_nntp_read(void *_ctx, int d, void *buf, size_t nbytes) { lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx; ssize_t rc; rc = read(d, buf, nbytes); 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 trace_nntp_write(void *_ctx, int d, const void *buf, size_t nbytes) { lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx; ssize_t rc; log3(ctx, TRACE, "NNTP %5d >> \"%{text}D\"", nbytes, buf, nbytes); rc = write(d, buf, nbytes); 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; va_start(ap, sig); if(sig == 0) { 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) { if ((sig == SIGHUP) || (sig == SIGINT) || (sig == SIGQUIT)) log1(ctx, NOTICE, "caught signal %d - exit - no more logging", sig); else log1(ctx, PANIC, "CAUGHT SIGNAL %d - EXIT - NO MORE LOGGING", sig); l2_stream_destroy(ctx->l2); } va_end(ap); exit(ERR_EXECUTION); } int main(int argc, char **argv) { lmtp_t *lmtp; lmtp_io_t lmtp_io; lmtp2nntp_t *ctx; int i; /* general purpose scratch int, index ... */ char *cp; /* general purpose character pointer */ char *azHosts; size_t asHosts; char *cpHost; char *cpPort; sa_t *sa; l2_channel_t *chPrefix; l2_channel_t *chBuf; l2_channel_t *chFile; /* 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); exit(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); exit(ERR_EXECUTION); } /* create application context */ if ((ctx = (lmtp2nntp_t *)malloc(sizeof(lmtp2nntp_t))) == NULL) exit(ERR_EXECUTION); ctx->progname = strdup(argv[0]); ctx->option_logfile = NULL; ctx->option_groupmode = GROUPMODE_ARG; ctx->option_deliverymode = DELIVERYMODE_FAKE; ctx->option_deliverymodefakestatus = "553"; /* Requested action not taken: mailbox name not allowed */ ctx->option_deliverymodefakedsn = "5.7.1"; /* Delivery not authorized, message refused */ ctx->option_maxmessagesize = 8 * 1024 * 1024; ctx->option_waittime = -1; ctx->option_mailfrom = NULL; ctx->option_levelmask = L2_LEVEL_NONE; ctx->l2 = NULL; ctx->cpBindh = NULL; ctx->cpBindp = 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].sa = NULL; ctx->ns[i].s = -1; ctx->ns[i].nntp = NULL; ctx->ns[i].rc = LMTP_ERR_UNKNOWN; } 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)); exit(ERR_EXECUTION); } #if 0 { char buf[1000]; int bufused = 0; int tracefile; for (i=0; i */ /* 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, "b:d:g:h:l:m:n:s:vw:")) != -1) { switch (i) { case 'b': /*POD [B<-b> I[I<:port>]] */ /* parse host[:port] string into host and 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 ((ctx->saBind = sa_create(SA_IP, "tcp", ctx->cpBindh, ctx->cpBindp)) == NULL) { fprintf(stderr, "%s:Error: creating TCP socket address failed for \"%s:%s\": %s\n", ctx->progname, ctx->cpBindh, ctx->cpBindp, strerror(errno)); exit(ERR_EXECUTION); } break; case 'd': /*POD [B<-d> I] */ if (strcasecmp(optarg, "post") == 0) ctx->option_deliverymode = DELIVERYMODE_POST; else if (strcasecmp(optarg, "feed") == 0) ctx->option_deliverymode = DELIVERYMODE_FEED; else { if (strlen(optarg) != 9) { fprintf(stderr, "%s:Error: Invalid format or length \"%s\" to option -d\n", ctx->progname, optarg); exit(ERR_EXECUTION); } if (optarg[3] != '/') { fprintf(stderr, "%s:Error: Invalid format or missing slash \"%s\" to option -d\n", ctx->progname, optarg); exit(ERR_EXECUTION); } optarg[3] = NUL; ctx->option_deliverymodefakestatus = &optarg[0]; ctx->option_deliverymodefakedsn = &optarg[4]; if ( strlen(ctx->option_deliverymodefakestatus) != 3 || !isdigit((int)ctx->option_deliverymodefakestatus[0]) || !isdigit((int)ctx->option_deliverymodefakestatus[1]) || !isdigit((int)ctx->option_deliverymodefakestatus[2])) { fprintf(stderr, "%s:Error: Invalid status in format \"%s\" to option -d\n", ctx->progname, optarg); exit(ERR_EXECUTION); } if ( (strlen(ctx->option_deliverymodefakedsn) != 5) || !isdigit((int)ctx->option_deliverymodefakedsn[0]) || (ctx->option_deliverymodefakedsn[1] != '.') || !isdigit((int)ctx->option_deliverymodefakedsn[2]) || (ctx->option_deliverymodefakedsn[3] != '.') || !isdigit((int)ctx->option_deliverymodefakedsn[4]) || (ctx->option_deliverymodefakedsn[0] != ctx->option_deliverymodefakestatus[0])) { fprintf(stderr, "%s:Error: Invalid dsn in format \"%s\" to option -d\n", ctx->progname, optarg); exit(ERR_EXECUTION); } } 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); exit(ERR_EXECUTION); } break; case 'h': /*POD [B<-h> I[I<:port>][,I[I<:port>], ...]] */ if (argz_create_sep(optarg, ',', &azHosts, &asHosts) != 0) exit(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 -h\n", ctx->progname, ctx->nsc); exit(ERR_EXECUTION); } /* parse host[:port] string into host and port */ 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 ((sa = sa_create(SA_IP, "tcp", ctx->ns[ctx->nsc].h, ctx->ns[ctx->nsc].p)) == NULL) { fprintf(stderr, "%s:Error: creating TCP socket address failed for \"%s:%s\": %s\n", ctx->progname, ctx->ns[ctx->nsc].h, ctx->ns[ctx->nsc].p, strerror(errno)); exit(ERR_EXECUTION); } ctx->ns[ctx->nsc].sa = sa; if ((ctx->ns[ctx->nsc].s = socket(sa->sa_buf->sa_family, SOCK_STREAM, sa->sa_proto)) == -1) { 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)); exit(ERR_EXECUTION); } ctx->ns[ctx->nsc].nntp = NULL; ctx->nsc++; } free(azHosts); 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); exit(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); exit(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); exit(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); exit(ERR_EXECUTION); } strcpy(ctx->uname.nodename, optarg); 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); exit(ERR_EXECUTION); } break; case 'v': /*POD [B<-v>] (version)*/ fprintf(stdout, "%s\n", lmtp2nntp_version.v_gnu); exit(0); break; case 'w': /*POD [B<-w> I] */ ctx->option_waittime = atoi(optarg); if(ctx->option_waittime < 1) { fprintf(stderr, "%s:Error: waittime %d to option -w must be greater 1 second.\n", ctx->progname, ctx->option_waittime); exit(ERR_EXECUTION); } break; case '?': default: usage(ctx->progname); exit(ERR_EXECUTION); } } /*POD I [I ...] */ for (i = optind; i < argc; i++) { argz_add(&ctx->azGroupargs, &ctx->asGroupargs, argv[i]); } if ((ctx->l2 = l2_stream_create()) == NULL) { fprintf(stderr, "%s:Error: logging failed to create stream\n", ctx->progname); exit(ERR_EXECUTION); } if (l2_stream_levels(ctx->l2, L2_LEVEL_UPTO(L2_LEVEL_DEBUG), L2_LEVEL_NONE) != L2_OK) { fprintf(stderr, "%s:Error: logging failed to set global logging level\n", ctx->progname); exit(ERR_EXECUTION); } if (l2_stream_formatter(ctx->l2, 'P', formatter_prefix, (l2_context_t *)&ctx) != L2_OK) { fprintf(stderr, "%s:Error: logging failed to register formatter\n", ctx->progname); exit(ERR_EXECUTION); } if (l2_stream_formatter(ctx->l2, 'D', l2_util_fmt_dump, NULL) != L2_OK) { fprintf(stderr, "%s:Error: logging failed to register dump formatter\n", ctx->progname); exit(ERR_EXECUTION); } if (l2_stream_formatter(ctx->l2, 'S', l2_util_fmt_string, NULL) != L2_OK) { fprintf(stderr, "%s:Error: logging failed to register string formatter\n", ctx->progname); exit(ERR_EXECUTION); } if (l2_stream_formatter(ctx->l2, 'm', formatter_errno, NULL) != L2_OK) { fprintf(stderr, "%s:Error: logging failed to register errno formatter\n", ctx->progname); exit(ERR_EXECUTION); } if((ctx->option_levelmask != L2_LEVEL_NONE) && (ctx->option_logfile != NULL)) { if ((chPrefix = l2_channel_create(&l2_handler_prefix)) == NULL) { fprintf(stderr, "%s:Error: logging failed to create prefix channel\n", ctx->progname); exit(ERR_EXECUTION); } if (l2_channel_configure(chPrefix, "prefix,timezone", "%b %d %H:%M:%S <%L> lmtp2nntp[%P]: ", "local") != L2_OK) { fprintf(stderr, "%s:Error: logging failed to configure prefix channel\n", ctx->progname); exit(ERR_EXECUTION); } if ((chBuf = l2_channel_create(&l2_handler_buffer)) == NULL) { fprintf(stderr, "%s:Error: logging failed to create buffer channel\n", ctx->progname); exit(ERR_EXECUTION); } if (l2_channel_configure(chBuf, "size", 65536) != L2_OK) { fprintf(stderr, "%s:Error: logging failed to configure buffer channel\n", ctx->progname); exit(ERR_EXECUTION); } if ((chFile = l2_channel_create(&l2_handler_file)) == NULL) { fprintf(stderr, "%s:Error: logging failed to create file channel\n", ctx->progname); exit(ERR_EXECUTION); } if (l2_channel_configure(chFile, "path,append,perm", ctx->option_logfile, TRUE, 0644) != L2_OK) { fprintf(stderr, "%s:Error: logging failed to configure file channel\n", ctx->progname); exit(ERR_EXECUTION); } if (l2_channel_stack(chFile, chBuf) != L2_OK) { fprintf(stderr, "%s:Error: logging failed to stack buffer channel on top of file channel\n", ctx->progname); exit(ERR_EXECUTION); } if (l2_channel_stack(chBuf, chPrefix) != L2_OK) { fprintf(stderr, "%s:Error: logging failed to stack prefix channel on top of buffer channel\n", ctx->progname); exit(ERR_EXECUTION); } if (l2_channel_open(chPrefix) != L2_OK) { fprintf(stderr, "%s:Error: logging failed to open buffer channel\n", ctx->progname); exit(ERR_EXECUTION); } if (l2_stream_channel(ctx->l2, chPrefix, ctx->option_levelmask, L2_LEVEL_NONE) != L2_OK) { fprintf(stderr, "%s:Error: logging failed to attach channel into stream\n", ctx->progname); exit(ERR_EXECUTION); } } if (log1(ctx, NOTICE, "startup, version %s", lmtp2nntp_version.v_gnu) != L2_OK) { fprintf(stderr, "%s:Error: logging failed to log startup message to stream\n", ctx->progname); exit(ERR_EXECUTION); } catchsignal(0, ctx); 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(SIGINFO, SIG_IGN ); signal(SIGUSR1, SIG_IGN ); signal(SIGUSR2, SIG_IGN ); /* initialize LMTP context */ lmtp_io.ctx = ctx; lmtp_io.select = NULL; lmtp_io.read = trace_lmtp_read; lmtp_io.write = trace_lmtp_write; if ((lmtp = lmtp_create(STDIN_FILENO, STDOUT_FILENO, (ctx->option_logfile && (ctx->option_levelmask >= L2_LEVEL_TRACE)) ? &lmtp_io : NULL )) == NULL) { fprintf(stderr, "%s:Error: Unable to initialize LMTP library\n", ctx->progname); exit(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); /* loop for LMTP protocol */ lmtp_loop(lmtp); /* graceful shutdown */ lmtp_gfs_quit(ctx); lmtp_gfs_lhlo(ctx); log0(ctx, NOTICE, "graceful shutdown shortly before exit - no more logging"); l2_stream_destroy(ctx->l2); lmtp_destroy(lmtp); 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 0; } /* taken from "UNIX Network Programming", Volume 1, second edition W. Richard * Stevens, connect_nonb.c from section 15.4 "Nonblocking connect", page 411, * http://www.kohala.com/start/ */ int connect_nonb(int sockfd, const struct sockaddr *saptr, socklen_t salen, int nsec) { int flags, n, error; socklen_t len; fd_set rset, wset; struct timeval tval; flags = fcntl(sockfd, F_GETFL, 0); fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); error = 0; if ( (n = connect(sockfd, (struct sockaddr *) saptr, salen)) < 0) if (errno != EINPROGRESS) return(-1); /* Do whatever we want while the connect is taking place. */ if (n == 0) goto done; /* connect completed immediately */ FD_ZERO(&rset); FD_SET(sockfd, &rset); wset = rset; tval.tv_sec = nsec; tval.tv_usec = 0; if ( (n = select(sockfd+1, &rset, &wset, NULL, nsec ? &tval : NULL)) == 0) { close(sockfd); /* timeout */ errno = ETIMEDOUT; return(-1); } if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) { len = sizeof(error); if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) return(-1); /* Solaris pending error */ } else return(-1); /* err_quit("select error: sockfd not set"); */ done: fcntl(sockfd, F_SETFL, flags); /* restore file status flags */ if (error) { close(sockfd); /* just in case */ errno = error; return(-1); } return(0); } 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); nntp_io.ctx = ctx; nntp_io.select = NULL; nntp_io.read = trace_nntp_read; nntp_io.write = trace_nntp_write; /* 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_deliverymode != DELIVERYMODE_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_deliverymode == DELIVERYMODE_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); if (bOk && (ctx->saBind != NULL)) { log2(ctx, DEBUG, "bind local socket to %s:%s", ctx->cpBindh, ctx->cpBindp); if (bind(ctx->ns[i].s, ctx->saBind->sa_buf, ctx->saBind->sa_len) < 0) { bOk = FALSE; log2(ctx, ERROR, "binding NNTP client to local address %s:%s failed, %m", ctx->cpBindh, ctx->cpBindp); } } if (bOk) { if(ctx->option_waittime > 0) { log1(ctx, DEBUG, "connect_nonb with waittime=%d", ctx->option_waittime); if (connect_nonb(ctx->ns[i].s, ctx->ns[i].sa->sa_buf, ctx->ns[i].sa->sa_len, ctx->option_waittime) < 0) { bOk = FALSE; log3(ctx, WARNING, "nonblocking connect to %s:%s with waittime=%d failed, %m", ctx->ns[i].h, ctx->ns[i].p, ctx->option_waittime); } } else { log0(ctx, DEBUG, "connect"); if (connect(ctx->ns[i].s, ctx->ns[i].sa->sa_buf, ctx->ns[i].sa->sa_len) < 0) { 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"); if ((ctx->ns[i].nntp = nntp_create(ctx->ns[i].s, ctx->ns[i].s, (ctx->option_logfile && (ctx->option_levelmask >= L2_LEVEL_TRACE)) ? &nntp_io : NULL)) == NULL) { bOk = FALSE; log0(ctx, ERROR, "creation of NNTP context failed"); } } if (bOk && ctx->option_waittime >= 0) { log1(ctx, DEBUG, "nntp_timeout with %d", ctx->option_waittime); nntp_timeout(ctx->ns[i].nntp, ctx->option_waittime); } 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); 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_deliverymode == DELIVERYMODE_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\n", /* 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_lhlo(lmtp2nntp_t *ctx) { int i; log0(ctx, TRACE, "LMTP service LHLO command - graceful shutdown"); for (i = 0; i < ctx->nsc; i++) { if (ctx->ns[i].nntp != NULL) nntp_destroy(ctx->ns[i].nntp); if (ctx->ns[i].s != -1) close(ctx->ns[i].s); if (ctx->ns[i].sa != NULL) sa_destroy(ctx->ns[i].sa); if (ctx->ns[i].p != NULL) free(ctx->ns[i].p); if (ctx->ns[i].h != NULL) free(ctx->ns[i].h); } 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); } 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 # ::= | # ::= | "-" # ::= | #