ossp-pkg/lmtp2nntp/lmtp2nntp.c
1.34
/*
* lmtp2nntp.c
*
* The lmtp2nntp program reads mail as a LMTP server and posts it to one or
* more newsgroups using NNTP. It delivers the message immediately or fails.
*
* The OSSP Project, Cable & Wireless Deutschland GmbH
* Thomas Lotterer, <thomas.lotterer@cw.com>
*
*/
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/utsname.h>
/* third party */
#include "str.h"
#include "argz.h"
#include "shpat_match.h"
/* own headers */
#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"
#include "trace.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 3
extern void lmtp_debug_dumplmtp(lmtp_t *lmtp);
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 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 {
int option_verbose;
int option_tracing;
int option_groupmode;
int option_deliverymode;
char *option_deliverymodefakestatus;
char *option_deliverymodefakedsn;
int option_maxmessagesize;
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 <lmtp2nntp.c -e 'while (<>) { if(m/\/\*POD (.*) .*\*\//) { $_=$1; s/.<(.*?)>/$1/g ; print "$_ " };}'
* to pull the USAGE string out of this source
*/
fprintf(stderr,
"USAGE: %s "
"[-d deliverymode] [-g groupmode] [-h host[:port]] "
"[-m maxmessagesize] [-t tracefile] [-v] [-V] newsgroup [newsgroup ...]"
"\n",
command);
return;
}
int main(int argc, char **argv)
{
int rc = 0;
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 *progname;
char *azHosts;
size_t asHosts;
char *cpHost;
char *cpPort;
sa_t *sa;
progname = argv[0];
/* create application context */
if ((ctx = (lmtp2nntp_t *)malloc(sizeof(lmtp2nntp_t))) == NULL)
exit(ERR_EXECUTION);
ctx->option_verbose = FALSE;
ctx->option_tracing = FALSE;
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->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", progname, strerror(errno));
exit(ERR_EXECUTION);
}
#if 1
{
char buf[1000];
int bufused = 0;
int tracefile;
for (i=0; i<argc; i++)
bufused+=sprintf(buf+bufused, "[%d]=\"%s\"\n", i, argv[i]);
if ((tracefile = open("/tmp/t", O_CREAT|O_WRONLY|O_APPEND, 0664)) != -1) {
write(tracefile, buf, bufused);
close(tracefile);
}
}
#endif
/*POD B<lmtp2nntp> */
/* use
* perl <lmtp2nntp.c -e 'while (<>) { if(m/\/\*POD (.*) .*\*\//) { $_=$1; print "$_\n" };}'
* to pull the POD SYNOPSIS header directly out of this source
*/
/* read in the arguments */
while ((i = getopt(argc, argv, "d:g:h:m:t:vV")) != -1) {
switch (i) {
case 'd': /*POD [B<-d> I<deliverymode>] */
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", progname, optarg);
exit(ERR_EXECUTION);
}
if (optarg[3] != '/') {
fprintf(stderr, "%s:Error: Invalid format or missing slash \"%s\" to option -d\n", 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", 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", progname, optarg);
exit(ERR_EXECUTION);
}
}
break;
case 'g': /*POD [B<-g> I<groupmode>] */
if (strcasecmp(optarg, "arg") == 0)
ctx->option_groupmode = GROUPMODE_ARG;
else if (strcasecmp(optarg, "envelope") == 0)
ctx->option_groupmode = GROUPMODE_ENVELOPE;
else if (strcasecmp(optarg, "header") == 0)
ctx->option_groupmode = GROUPMODE_HEADER;
else {
fprintf(stderr, "%s:Error: Invalid mode \"%s\" to option -g\n", progname, optarg);
exit(ERR_EXECUTION);
}
break;
case 'h': /*POD [B<-h> I<host>[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", 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",
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",
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 'm': /*POD [B<-m> I<maxmessagesize>] */
ctx->option_maxmessagesize = atoi(optarg);
if(ctx->option_maxmessagesize < 64) {
fprintf(stderr, "%s:Error: maximum message size is unacceptable small.\n", progname);
exit(ERR_EXECUTION);
}
break;
case 't': /*POD [B<-t> I<tracefile>] */
ctx->option_tracing = TRUE;
trace_read (-1, optarg, 0);
trace_write(-1, optarg, 0);
break;
case 'v': /*POD [B<-v>] (verbose)*/
ctx->option_verbose = TRUE;
break;
case 'V': /*POD [B<-V>] (version)*/
fprintf(stdout, "%s\n", lmtp2nntp_version.v_gnu);
exit(0);
break;
case '?':
default:
usage(progname);
exit(ERR_EXECUTION);
}
}
/*POD I<newsgroup> [I<newsgroup> ...] */
for (i = optind; i < argc; i++) {
argz_add(&ctx->azGroupargs, &ctx->asGroupargs, argv[i]);
}
/* initialize LMTP context */
lmtp_io.select = NULL;
lmtp_io.read = trace_read;
lmtp_io.write = trace_write;
if ((lmtp = lmtp_create(STDIN_FILENO, STDOUT_FILENO, ctx->option_tracing ? &lmtp_io : NULL)) == NULL) {
fprintf(stderr, "%s:Error: Unable to initialize LMTP library\n", 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 <SP> <domain> <CRLF>
* MAIL <SP> FROM:<reverse-path> <CRLF>
* RCPT <SP> TO:<forward-path> <CRLF>
* DATA <CRLF>
* RSET <CRLF>
* NOOP <CRLF>
* QUIT <CRLF>
*/
lmtp_register(lmtp, "LHLO", lmtp_cb_lhlo, ctx, NULL, NULL);
lmtp_register(lmtp, "MAIL", lmtp_cb_mail, ctx, NULL, NULL);
lmtp_register(lmtp, "RCPT", lmtp_cb_rcpt, ctx, NULL, NULL);
lmtp_register(lmtp, "DATA", lmtp_cb_data, ctx, NULL, NULL);
lmtp_register(lmtp, "RSET", lmtp_cb_rset, ctx, NULL, NULL);
lmtp_register(lmtp, "NOOP", lmtp_cb_noop, ctx, NULL, NULL);
lmtp_register(lmtp, "QUIT", lmtp_cb_quit, ctx, NULL, NULL);
/* loop for LMTP protocol */
lmtp_loop(lmtp);
/* graceful shutdown */
lmtp_gfs_quit(ctx);
lmtp_gfs_lhlo(ctx);
lmtp_destroy(lmtp);
if (ctx->azGroupargs != NULL)
free(ctx->azGroupargs);
if (ctx != NULL)
free(ctx);
str_parse(NULL, NULL);
return rc;
}
static void resetsession(struct session *session)
{
if (session->lhlo_domain != NULL)
free(session->lhlo_domain);
initsession(session);
return;
}
static void initsession(struct session *session)
{
session->lhlo_seen = FALSE;
session->lhlo_domain = NULL;
return;
}
static lmtp_rc_t lmtp_cb_lhlo(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx)
{
/*
* RFC0821 [excerpt] 4.1. SMTP COMMANDS
* 4.1.1. COMMAND SEMANTICS, HELO
* This command and an OK reply to it confirm that both the sender-SMTP
* and the receiver-SMTP are in the initial state, that is, there is no
* transaction in progress and all state tables and buffers are cleared.
*
* The first command in a session must be the HELO command. The HELO
* command may be used later in a session as well. If the HELO command
* argument is not acceptable a 501 failure reply must be returned and
* the receiver-SMTP must stay in the same state.
*
* If the transaction beginning command argument is not acceptable a 501
* failure reply must be returned and the receiver-SMTP must stay in the
* same state. If the commands in a transaction are out of order a 503
* failure reply must be returned and the receiver-SMTP must stay in the
* same state.
*
* HELO <SP> <domain> <CRLF>
*/
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
lmtp_res_t res;
nntp_rc_t rc;
char str[STDSTRLEN];
int bOk;
int i;
nntp_io_t nntp_io;
nntp_io.select = NULL;
nntp_io.read = trace_read;
nntp_io.write = trace_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
*/
if (ctx->session.lhlo_seen) {
res.statuscode = "503";
res.dsncode = "5.0.0";
res.statusmsg = "Duplicate LHLO.";
lmtp_response(lmtp, &res);
return 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
*/
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.";
lmtp_response(lmtp, &res);
return LMTP_OK;
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 451 Requested action aborted: local error in processing
* RFC1893 2. Status Codes 4.X.X Persistent Transient Failure
* RFC1893 3.5 Network and Routing Status X.3.5 System incorrectly configured
*/
if (ctx->nsc == 0) {
res.statuscode = "451";
res.dsncode = "4.3.5";
res.statusmsg = "No valid NNTP services configured.";
lmtp_response(lmtp, &res);
return LMTP_OK;
}
i = 0;
do {
bOk = TRUE;
if (connect(ctx->ns[i].s, ctx->ns[i].sa->sa_buf, ctx->ns[i].sa->sa_len) < 0) {
bOk = FALSE;
}
if (bOk && ((ctx->ns[i].nntp = nntp_create(ctx->ns[i].s, ctx->ns[i].s,
ctx->option_tracing ? &nntp_io : NULL)) == NULL)) {
bOk = FALSE;
}
if (bOk && ((rc = nntp_init(ctx->ns[i].nntp)) != NNTP_OK)) {
bOk = FALSE;
}
if (bOk)
i++;
else {
if (i < --ctx->nsc) {
memcpy(&ctx->ns[i], &ctx->ns[i+1], (ctx->nsc - i ) * sizeof(struct ns));
}
}
} while (i < ctx->nsc);
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 421 <domain> Service not available
* RFC1893 2. Status Codes 4.X.X Persistent Transient Failure
* RFC1893 3.5 Network and Routing Status X.4.1 No answer from host
*/
if (ctx->nsc == 0) {
res.statuscode = "421";
res.dsncode = "4.4.1";
res.statusmsg = "All attempts connecting to NNTP services failed.";
lmtp_response(lmtp, &res);
return 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;
lmtp_response(lmtp, &res);
return LMTP_OK;
}
static void lmtp_gfs_lhlo(lmtp2nntp_t *ctx)
{
/* graceful shutdown */
int i;
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);
}
}
static int helo_rfc0821domain(char *msg, char **domain)
{
int rc;
rc = str_parse(msg,
"^.+ ("
/*
##
## The mega Perl regular expression below is generated
## with the following Perl program. This is only possible
## because the given grammar is Chomsky-3 (right or left
## linear grammar, but noth both).
##
# BNF grammar for <domain> according to RFC0821:
# <snum> ::= one, two, or three digits representing a decimal integer value in the range 0 through 255
# <a> ::= any one of the 52 alphabetic characters A through Z in upper case and a through z in lower case
# <d> ::= any one of the ten digits 0 through 9
# <let-dig-hyp> ::= <a> | <d> | "-"
# <let-dig> ::= <a> | <d>
# <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
# <dotnum> ::= <snum> "." <snum> "." <snum> "." <snum>
# <number> ::= <d> | <d> <number>
# <name> ::= <a> <ldh-str> <let-dig>
# <element> ::= <name> | "#" <number> | "[" <dotnum> "]"
# <domain> ::= <element> | <element> "." <domain>
#
# corresponding Perl regular expression ($domain)
$snum = "(?:[0-9]|[0-9]{2}|[0-1][0-9]{2}|2[0-4][0-9]|25[0-5])";
$d = "[0-9]";
$a = "[A-Za-z]";
$let_dig_hyp = "(?:$a|$d|-)";
$let_dig = "(?:$a|$d)";
$ldh_str = "${let_dig_hyp}+";
$dotnum = "$snum\\.$snum\\.$snum\\.$snum";
$number = "$d+";
$name = "$a$ldh_str$let_dig";
$element = "(?:$name|#$number|\\[$dotnum\\])";
$domain = "(?:$element\.)*$element";
#
# translate into C string block suitable for passing to the Perl
# Compatible Regular Expressions (PCRE) based string library Str.
my $cregex = $domain;
$cregex .= "\n";
$cregex =~ s|\\|\\\\|sg;
$cregex =~ s|(.{17})|$1\n|sg;
$cregex =~ s|([^\n]+)\n|"$1"\n|sg;
$cregex =~ s|\n\n|\n|sg;
print "$cregex";
*/
"(?:(?:[A-Za-z](?:[A-Za-z]|[0-9]|-)+(?:[A-Za-z]|[0-9])|#[0-9]+|\\[(?:[0"
"-9]|[0-9]{2}|[0-1][0-9]{2}|2[0-4][0-9]|25[0-5])\\.(?:[0-9]|[0-9]{2}|[0"
"-1][0-9]{2}|2[0-4][0-9]|25[0-5])\\.(?:[0-9]|[0-9]{2}|[0-1][0-9]{2}|2[0"
"-4][0-9]|25[0-5])\\.(?:[0-9]|[0-9]{2}|[0-1][0-9]{2}|2[0-4][0-9]|25[0-5"
"])\\]).)*(?:[A-Za-z](?:[A-Za-z]|[0-9]|-)+(?:[A-Za-z]|[0-9])|#[0-9]+|\\"
"[(?:[0-9]|[0-9]{2}|[0-1][0-9]{2}|2[0-4][0-9]|25[0-5])\\.(?:[0-9]|[0-9]"
"{2}|[0-1][0-9]{2}|2[0-4][0-9]|25[0-5])\\.(?:[0-9]|[0-9]{2}|[0-1][0-9]{"
"2}|2[0-4][0-9]|25[0-5])\\.(?:[0-9]|[0-9]{2}|[0-1][0-9]{2}|2[0-4][0-9]|"
"25[0-5])\\])"
")$", domain);
return rc;
}
static int helo_rfc1035domain(char *msg, char **domain)
{
int rc;
rc = str_parse(msg,
"^.+ ("
/*
##
## The mega Perl regular expression below is generated
## with the following Perl program. This is only possible
## because the given grammar is Chomsky-3 (right or left
## linear grammar, but noth both).
##
# BNF grammar for <domain> according to RFC1035:
# <letter> ::= any one of the 52 alphabetic characters A through Z in upper case and a through z in lower case
# <digit> ::= any one of the ten digits 0 through 9
# <let-dig> ::= <letter> | <digit>
# <let-dig-hyp> ::= <let-dig> | "-"
# <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
# <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
# <subdomain> ::= <label> | <subdomain> "." <label>
# <domain> ::= <subdomain> | " "
#
# corresponding Perl regular expression ($domain)
$letter = "[A-Za-z]";
$digit = "[0-9]";
$let_dig = "(?:$letter|$digit)";
$let_dig_hyp = "(?:$let_dig|-)";
$ldh_str = "${let_dig_hyp}+";
$label = "(?:$letter(?:(?:$ldh_str)?$let_dig)?)";
$subdomain = "(?:$label\.)*$label";
$domain = "(?:$subdomain| )";
#
# translate into C string block suitable for passing to the Perl
# Compatible Regular Expressions (PCRE) based string library Str.
my $cregex = $domain;
$cregex .= "\n";
$cregex =~ s|\\|\\\\|sg;
$cregex =~ s|(.{17})|$1\n|sg;
$cregex =~ s|([^\n]+)\n|"$1"\n|sg;
$cregex =~ s|\n\n|\n|sg;
print "$cregex";
*/
"(?:(?:(?:[A-Za-z](?:(?:(?:(?:[A-Za-z]|[0-9])|-)+)?(?:[A-Za-z]|[0-9]))?"
").)*(?:[A-Za-z](?:(?:(?:(?:[A-Za-z]|[0-9])|-)+)?(?:[A-Za-z]|[0-9]))?)|"
" )"
")$", domain);
return rc;
}
static lmtp_rc_t lmtp_cb_mail(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx)
{
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
lmtp_res_t res;
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 553 Requested action not taken: mailbox name not allowed
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.1.8 Bad sender's system address
*/
if (!ctx->session.lhlo_seen) {
res.statuscode = "553";
res.dsncode = "5.1.8";
res.statusmsg = "friendly people say LHLO to open a transmission channel.";
lmtp_response(lmtp, &res);
return LMTP_OK;
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 503 Bad sequence of commands
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.5.0 Other or undefined protocol status
*/
if (ctx->msg != NULL) {
res.statuscode = "503";
res.dsncode = "5.5.0";
res.statusmsg = "Sender already specified.";
lmtp_response(lmtp, &res);
return LMTP_OK;
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 452 Requested action not taken: insufficient system storage
* RFC1893 2. Status Codes 4.X.X Persistent Transient Failure
* RFC1893 3.5 Network and Routing Status X.3.1 Mail system full
*/
if ((ctx->msg = msg_create()) == NULL) {
res.statuscode = "452";
res.dsncode = "4.3.1";
res.statusmsg = "Internal error - memory.";
lmtp_response(lmtp, &res);
return LMTP_ERR_MEM;
}
/* RFC1652 2. Framework for the 8bit MIME Transport Extension
* (4) one optional parameter using the keyword BODY is added to the MAIL
* FROM command. The value associated with this parameter is a keyword
* indicating whether a 7bit message [...] or a MIME message [...] is
* being sent. The syntax of the value is as follows, using the ABNF
* notation [...]
*
* body-value ::= "7BIT" / "8BITMIME"
*
* "MAIL From:<foo@bar>"
* "MAIL From:<foo@bar> BODY=8BITMIME"
* "MAIL From:<foo@bar> BODY=7BIT"
*
* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 553 Requested action not taken: mailbox name not allowed
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.1.7 Bad sender's mailbox address syntax
*/
if (str_parse(req->msg, "m/^MAIL From:\\s*<(?:.+@.+)>/i") <= 0) {
res.statuscode = "553";
res.dsncode = "5.1.7";
res.statusmsg = "Domain name required for sender address.";
lmtp_response(lmtp, &res);
msg_destroy(ctx->msg);
ctx->msg = NULL;
return LMTP_OK;
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 501 Syntax error in parameters or arguments
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.5.4 Invalid command arguments
*/
if (str_parse(req->msg, "m/^MAIL From:\\s*<(.+@.+)>"
"(?:\\s+BODY=(?:7BIT|8BITMIME)\\s*)?$/i",
&ctx->msg->mail_from) <= 0) {
res.statuscode = "501";
res.dsncode = "5.5.4";
res.statusmsg = "Unknown parameter for keyword BODY.";
lmtp_response(lmtp, &res);
msg_destroy(ctx->msg);
ctx->msg = NULL;
return LMTP_OK;
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 250 Requested mail action okay, completed
* RFC1893 2. Status Codes 2.X.X Success
* RFC1893 3.5 Network and Routing Status X.1.0 Other address status
*/
res.statuscode = "250";
res.dsncode = "2.1.0";
res.statusmsg = "Sender ok.";
lmtp_response(lmtp, &res);
return LMTP_OK;
}
static lmtp_rc_t lmtp_cb_rcpt(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx)
{
lmtp_res_t res;
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
char *cp;
char *group;
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 503 Bad sequence of commands
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.5.0 Other or undefined protocol status
*/
if ((ctx->msg == NULL) || (ctx->msg->mail_from == NULL)) {
res.statuscode = "503";
res.dsncode = "5.5.0";
res.statusmsg = "specify sender with MAIL first.";
lmtp_response(lmtp, &res);
return LMTP_OK;
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 501 Syntax error in parameters or arguments
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.5.2 Syntax error
*/
if (str_parse(req->msg, "m/^RCPT To:\\s*(.+)$/i", &cp) <= 0) {
res.statuscode = "501";
res.dsncode = "5.5.2";
res.statusmsg = "Syntax error in parameters.";
lmtp_response(lmtp, &res);
return LMTP_OK;
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 550 Requested action not taken: mailbox unavailable
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.1.1 Bad destination mailbox address
*/
if ((cp == NULL) || (strlen(cp) == 0)) {
res.statuscode = "550";
res.dsncode = "5.1.1";
res.statusmsg = "nul Recipient/ Group.";
lmtp_response(lmtp, &res);
return LMTP_OK;
}
/* in GROUPMODE = ARG|HEADER recipient must be acknowledged and stored to
* give proper pipelining responses. in GROUPMODE = ENVELOPE recipient is
* transformed into a group and matched against groupfilter. Only valid
* groups are stored to give proper pipelining responses.
*
* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 550 Requested action not taken: mailbox unavailable
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.1.1 Bad destination mailbox address
* X.7.2 Mailing list expansion prohibited
*/
if (ctx->option_groupmode == GROUPMODE_ENVELOPE) {
if (str_parse(cp, "m/^<(.+)?@[^@]+>$/i", &group) <= 0) {
res.statuscode = "550";
res.dsncode = "5.1.1";
res.statusmsg = "Recipient did not transform into group.";
lmtp_response(lmtp, &res);
return LMTP_OK;
}
if (!groupmatch(ctx->azGroupargs, ctx->asGroupargs, group)) {
res.statuscode = "550";
res.dsncode = "5.7.2";
res.statusmsg = "unmatched Group.";
lmtp_response(lmtp, &res);
return LMTP_OK;
}
argz_add(&ctx->msg->azEnvgroups, &ctx->msg->asEnvgroups, group);
}
argz_add(&ctx->msg->azRcpt, &ctx->msg->asRcpt, cp);
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 250 Requested mail action okay, completed
* RFC1893 2. Status Codes 2.X.X Success
* RFC1893 3.5 Network and Routing Status X.1.5 Destination address valid
*/
res.statuscode = "250";
res.dsncode = "2.1.5";
res.statusmsg = ctx->option_groupmode == GROUPMODE_ENVELOPE ? "Group accepted." : "Recipient accepted.";
lmtp_response(lmtp, &res);
return LMTP_OK;
}
int groupmatch(char *azPattern, size_t asPattern, char *cpGroup)
{
int bGroupmatch;
char *cpGroupmatch;
bGroupmatch = FALSE;
cpGroupmatch = NULL;
while ((cpGroupmatch = argz_next(azPattern, asPattern, cpGroupmatch)) != NULL) {
if (shpat_match(cpGroupmatch, cpGroup, 0) == 0)
bGroupmatch = TRUE;
}
return bGroupmatch;
}
static lmtp_rc_t lmtp_cb_data(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx)
{
lmtp_res_t res;
lmtp_rc_t rc = LMTP_OK;
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
char *azErr;
size_t asErr;
char errorstring[STDSTRLEN];
char *rcpt;
int i;
int bSuccess;
char *cp;
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 503 Bad sequence of commands
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.5.0 Other or undefined protocol status
*/
if ((ctx->msg == NULL) || (argz_count(ctx->msg->azRcpt, ctx->msg->asRcpt) == 0)) {
res.statuscode = "503";
res.dsncode = "5.5.0";
res.statusmsg = "specify recipient with RCPT first.";
lmtp_response(lmtp, &res);
return LMTP_OK;
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 354 Start mail input; end with <CRLF>.<CRLF>
*/
res.statuscode = "354";
res.dsncode = NULL; /* DSN not used for data */
res.statusmsg = "Enter mail, end with \".\" on a line by itself";
lmtp_response(lmtp, &res);
rc = lmtp_readmsg(lmtp, &ctx->msg->cpMsg, ctx->option_maxmessagesize);
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 552 Requested mail action aborted: exceeded storage allocation
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.2.3 Message length exceeds administrative limit.
*/
if (rc == LMTP_ERR_OVERFLOW) {
str_format(errorstring, sizeof(errorstring), "Message length exceeds administrative limit. %s", lmtp_error(rc));
res.statuscode = "552";
res.dsncode = "5.2.3";
res.statusmsg = errorstring;
rcpt = NULL;
while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
lmtp_response(lmtp, &res);
}
return LMTP_OK;
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 451 Requested action aborted: local error in processing
* RFC1893 2. Status Codes 4.X.X Persistent Transient Failure
* RFC1893 3.5 Network and Routing Status X.3.2 System not accepting network messages
*/
if (rc == LMTP_ERR_SYSTEM) {
str_format(errorstring, sizeof(errorstring), "System error reading message: %s", strerror(errno));
res.statuscode = "451";
res.dsncode = "4.3.2";
res.statusmsg = errorstring;
rcpt = NULL;
while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
lmtp_response(lmtp, &res);
}
return LMTP_OK;
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 451 Requested action aborted: local error in processing
* RFC1893 2. Status Codes 4.X.X Persistent Transient Failure
* RFC1893 3.5 Network and Routing Status X.3.2 System not accepting network messages
*/
if(rc != LMTP_OK) {
str_format(errorstring, sizeof(errorstring), "Unknown error reading message: %s", lmtp_error(rc));
res.statuscode = "451";
res.dsncode = "4.3.2";
res.statusmsg = errorstring;
rcpt = NULL;
while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
lmtp_response(lmtp, &res);
}
return LMTP_OK;
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 554 Transaction failed
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.6.5 Conversion Failed
*/
if ((rc = msg_split(ctx->msg)) != MSG_OK) {
str_format(errorstring, sizeof(errorstring), "Error splitting message: %s", msg_error(rc));
res.statuscode = "554";
res.dsncode = "5.6.5";
res.statusmsg = errorstring;
rcpt = NULL;
while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
lmtp_response(lmtp, &res);
}
return LMTP_OK;
}
if (ctx->option_groupmode == GROUPMODE_ENVELOPE) {
if ((cp = malloc(ctx->msg->asEnvgroups + 1)) == NULL) {
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 452 Requested action not taken: insufficient system storage
* RFC1893 2. Status Codes 4.X.X Persistent Transient Failure
* RFC1893 3.5 Network and Routing Status X.3.1 Mail system full
*/
if ((ctx->msg = msg_create()) == NULL) {
res.statuscode = "452";
res.dsncode = "4.3.1";
res.statusmsg = "Internal error - memory.";
lmtp_response(lmtp, &res);
return LMTP_ERR_MEM;
}
}
ctx->msg->azNewsgroups = memcpy(cp, ctx->msg->azEnvgroups, ctx->msg->asEnvgroups);
ctx->msg->asNewsgroups = ctx->msg->asEnvgroups;
}
else if (ctx->option_groupmode == GROUPMODE_ARG) {
if ((cp = malloc(ctx->asGroupargs + 1)) == NULL) {
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 452 Requested action not taken: insufficient system storage
* RFC1893 2. Status Codes 4.X.X Persistent Transient Failure
* RFC1893 3.5 Network and Routing Status X.3.1 Mail system full
*/
if ((ctx->msg = msg_create()) == NULL) {
res.statuscode = "452";
res.dsncode = "4.3.1";
res.statusmsg = "Internal error - memory.";
lmtp_response(lmtp, &res);
return LMTP_ERR_MEM;
}
}
ctx->msg->azNewsgroups = memcpy(cp, ctx->azGroupargs, ctx->asGroupargs);
ctx->msg->asNewsgroups = ctx->asGroupargs;
}
else { /* == GROUPMODE_HEADER */
cp = ctx->msg->azNewsgroups;
while (cp != NULL) {
if (!groupmatch(ctx->azGroupargs, ctx->asGroupargs, cp)) {
if (argz_next(ctx->msg->azNewsgroups, ctx->msg->asNewsgroups, cp) == NULL) {
argz_delete(&ctx->msg->azNewsgroups, &ctx->msg->asNewsgroups, cp);
break;
}
else
argz_delete(&ctx->msg->azNewsgroups, &ctx->msg->asNewsgroups, cp);
} else {
cp = argz_next(ctx->msg->azNewsgroups, ctx->msg->asNewsgroups, cp);
}
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 550 Requested action not taken: mailbox unavailable
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.7.2 Mailing list expansion prohibited
*/
if (ctx->msg->asNewsgroups == 0) {
rcpt = NULL;
while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
res.statuscode = "550";
res.dsncode = "5.7.2";
res.statusmsg = "Header did not match any valid group.";
lmtp_response(lmtp, &res);
}
return LMTP_OK;
}
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 554 Transaction failed
* RFC1893 2. Status Codes 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.6.5 Conversion Failed
*/
if ((rc = msg_join(ctx->msg)) != MSG_OK) {
str_format(errorstring, sizeof(errorstring), "Error joining message: %s", msg_error(rc));
res.statuscode = "554";
res.dsncode = "5.6.5";
res.statusmsg = errorstring;
rcpt = NULL;
while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
lmtp_response(lmtp, &res);
}
return LMTP_OK;
}
bSuccess = NNTP_EOF; /* assume a hard error for the worst case */
for (i = 0; i < ctx->nsc; i++) {
switch (ctx->option_deliverymode) {
case DELIVERYMODE_FAKE:
break;
case DELIVERYMODE_POST:
ctx->ns[i].rc = nntp_post(ctx->ns[i].nntp, ctx->msg);
break;
case DELIVERYMODE_FEED:
ctx->ns[i].rc = nntp_feed(ctx->ns[i].nntp, ctx->msg);
break;
}
if (ctx->ns[i].rc == NNTP_OK)
bSuccess = NNTP_OK;
if ( bSuccess != NNTP_OK
&& (
(ctx->ns[i].rc == NNTP_TIMEOUT)
|| (ctx->ns[i].rc == NNTP_ERR_SYSTEM)
|| (ctx->ns[i].rc == NNTP_DEFER)
)
)
bSuccess = NNTP_DEFER;
}
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 250 Requested mail action okay, completed
* 451 Requested action aborted: local error in processing
* 554 Transaction failed
* RFC1893 2. Status Codes 2.X.X Success
* 4.X.X Persistent Transient Failure
* 5.X.X Permanent Failure
* RFC1893 3.5 Network and Routing Status X.0.0 Other undefined Status
* X.4.2 Bad connection
*/
rcpt = NULL;
while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
if (ctx->option_deliverymode == DELIVERYMODE_FAKE) {
res.statuscode = ctx->option_deliverymodefakestatus;
res.dsncode = ctx->option_deliverymodefakedsn;
str_format(errorstring, sizeof(errorstring),
"NNTP noop fake return for %s", rcpt);
} else {
switch (bSuccess) {
case NNTP_OK:
str_format(errorstring, sizeof(errorstring),
"Message accepted for delivery to %s", rcpt);
res.statuscode = "250";
res.dsncode = "2.0.0";
break;
case NNTP_DEFER:
str_format(errorstring, sizeof(errorstring),
"Requested action aborted for %s, local error in processing.", rcpt);
res.statuscode = "451";
res.dsncode = "4.4.2";
break;
default:
str_format(errorstring, sizeof(errorstring),
"Error sending article for %s.", rcpt);
res.statuscode = "554";
res.dsncode = "5.4.2";
break;
}
}
azErr = NULL;
asErr = 0;
argz_add(&azErr, &asErr, errorstring);
for (i = 0; i < ctx->nsc; i++) {
if (ctx->ns[i].rc != NNTP_OK) {
str_format(errorstring, sizeof(errorstring), "%s:%s returned %s", ctx->ns[i].h, ctx->ns[i].p, nntp_error(ctx->ns[i].rc));
argz_add(&azErr, &asErr, errorstring);
}
}
if (azErr != NULL) {
argz_stringify(azErr, asErr, '\n');
res.statusmsg = azErr;
lmtp_response(lmtp, &res);
free(azErr);
azErr = NULL;
asErr = 0;
}
else {
res.statusmsg = errorstring;
lmtp_response(lmtp, &res);
}
}
msg_destroy(ctx->msg);
ctx->msg = NULL;
return LMTP_OK;
}
static lmtp_rc_t lmtp_cb_noop(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *ctx)
{
lmtp_res_t res;
lmtp_rc_t rc = LMTP_OK;
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 250 Requested mail action okay, completed
* RFC1893 2. Status Codes 2.X.X Success
* RFC1893 3.5 Network and Routing Status X.0.0 Other undefined Status
*/
res.statuscode = "250";
res.dsncode = "2.0.0";
res.statusmsg = "OK. Nice talking to you.";
lmtp_response(lmtp, &res);
return rc;
}
static lmtp_rc_t lmtp_cb_rset(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx)
{
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
lmtp_res_t res;
lmtp_rc_t rc = LMTP_OK;
lmtp_gfs_rset(ctx);
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 250 Requested mail action okay, completed
* RFC1893 2. Status Codes 2.X.X Success
* RFC1893 3.5 Network and Routing Status X.0.0 Other undefined Status
*/
res.statuscode = "250";
res.dsncode = "2.0.0";
res.statusmsg = "Reset state.";
lmtp_response(lmtp, &res);
return rc;
}
static void lmtp_gfs_rset(lmtp2nntp_t *ctx)
{
/* graceful shutdown */
if (ctx->msg != NULL) {
msg_destroy(ctx->msg);
ctx->msg = NULL;
}
}
static lmtp_rc_t lmtp_cb_quit(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx)
{
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
lmtp_res_t res;
lmtp_rc_t rc = LMTP_EOF;
lmtp_gfs_quit(ctx);
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 221 <domain> Service closing transmission channel
* RFC1893 2. Status Codes 2.X.X Success
* RFC1893 3.5 Network and Routing Status X.0.0 Other undefined Status
*/
res.statuscode = "221";
res.dsncode = "2.0.0";
res.statusmsg = "LMTP Service closing transmission channel.";
lmtp_response(lmtp, &res);
return rc;
}
static void lmtp_gfs_quit(lmtp2nntp_t *ctx)
{
/* graceful shutdown */
lmtp_gfs_rset(ctx);
resetsession(&ctx->session);
}