ossp-pkg/lmtp2nntp/lmtp2nntp.c
1.12
/*
* 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>
/* third party */
#include "str.h"
#include "argz.h"
/* own headers */
#include "lmtp.h"
#include "nntp.h"
#include "sa.h"
#ifndef FALSE
#define FALSE (1 != 1)
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif
#define ERR_EXECUTION -1
#define ERR_DELIVERY -2
#define MESSAGE_MAXLEN 8*1024*1024
#define STDSTRLEN 128
#define MAXNEWSSERVICES 3
extern void lmtp_debug_dumplmtp(lmtp_t *lmtp);
static ssize_t trace_read(int d, void *buf, size_t nbytes);
static ssize_t trace_write(int d, const void *buf, size_t nbytes);
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 lmtp_rc_t prepareheader(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);
struct message {
char *cpMsg; /* the wholly message to be received by DATA command */
char *cpHeaders; /* header part of message above */
char *cpBody; /* body part of message above */
char *mail_from;
char *azRcpt;
size_t asRcpt;
char *azHeaders;
size_t asHeaders;
};
static void initmessage(struct message *message);
static void resetmessage(struct message *message);
struct ns {
char *h; /* host */
char *p; /* port */
sa_t *sa;
int s; /* socket */
nntp_t *nntp;
};
typedef struct {
int option_verbose;
int option_tracing;
int option_groupmode;
int nsc;
struct ns ns[MAXNEWSSERVICES];
char *azGroups;
size_t asGroups;
char *azGroupfilters;
size_t asGroupfilters;
struct session session;
struct message message;
} lmtp2nntp_t;
enum {
GROUPMODE_ARG,
GROUPMODE_ENVELOPE,
GROUPMODE_HEADER
};
/*
* tracing
*/
ssize_t trace_read(int d, void *buf, size_t nbytes)
{
ssize_t rc;
int tracefile;
rc = read(d, buf, nbytes);
if ((tracefile = open("/tmp/t", O_CREAT|O_WRONLY|O_APPEND, 0664)) != -1) {
write(tracefile, buf, rc);
close(tracefile);
}
return rc;
}
ssize_t trace_write(int d, const void *buf, size_t nbytes)
{
ssize_t rc;
int tracefile;
rc = write(d, buf, nbytes);
if ((tracefile = open("/tmp/t", O_CREAT|O_WRONLY|O_APPEND, 0664)) != -1) {
write(tracefile, buf, rc);
close(tracefile);
}
return rc;
}
/*
* print usage information
*/
static void usage(char *command)
{
fprintf(stderr,
"USAGE: %s [-p protocol] [-l logtarget] "
"[-h host[:port]] [-m mode] [-t] [-v] newsgroup [newsgroup ...]\n",
command);
return;
}
int main(int argc, char **argv)
{
//FIXME int rc = 0;
lmtp_t *lmtp;
lmtp_io_t lmtp_io;
lmtp2nntp_t *ctx;
int i; /* general purpose scratch int, index ... */
char *progname;
char *cpHost;
char *cpPort;
sa_t *sa;
#if 0
/* begin NNTP posting test */
{
nntp_post(nntp, "...");
nntp_destroy(nntp);
sock_destroy(s);
exit(0);
}
#endif
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->nsc = 0;
for (i=0; i < MAXNEWSSERVICES; i++) {
ctx->ns[i].h = "";
ctx->ns[i].s = -1;
ctx->ns[i].nntp = NULL;
}
ctx->azGroups = NULL;
ctx->asGroups = 0;
ctx->azGroupfilters = NULL;
ctx->asGroupfilters = 0;
initsession(&ctx->session);
initmessage(&ctx->message);
{
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);
}
}
/* read in the arguments */
while ((i = getopt(argc, argv, "g:h:tv")) != -1) {
switch (i) {
case 'g': /* -g 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': /* -h host */
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(optarg);
if ((cpPort = strrchr(cpHost, ':')) != NULL) {
*cpPort++ = '\0';
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);
}
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].sa = sa;
//FIXME sa_destroy(sa);
ctx->ns[ctx->nsc].nntp = NULL;
ctx->nsc++;
break;
case 't': // -t (tracing)
ctx->option_tracing = TRUE;
break;
case 'v': // -v (verbose)
ctx->option_verbose = TRUE;
break;
case '?':
default:
usage(progname);
exit(ERR_EXECUTION);
}
}
/* remaining arguments are groups or group filters */
for (i = optind; i < argc; i++)
if (ctx->option_groupmode == GROUPMODE_ENVELOPE)
argz_add(&ctx->azGroupfilters, &ctx->asGroupfilters, argv[i]);
else
argz_add(&ctx->azGroups, &ctx->asGroups, argv[i]);
/* initialize LMTP context */
lmtp_io.read = trace_read;
lmtp_io.write = trace_write;
if ((lmtp = lmtp_create(STDIN_FILENO, STDOUT_FILENO, &lmtp_io)) == NULL) {
fprintf(stderr, "%s:Error: Unable to initialize LMTP library\n", progname);
exit(ERR_EXECUTION);
}
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, "NOOP", lmtp_cb_noop, ctx, NULL, NULL);
lmtp_register(lmtp, "RSET", lmtp_cb_rset, ctx, NULL, NULL);
lmtp_register(lmtp, "QUIT", lmtp_cb_quit, ctx, NULL, NULL);
/* loop for LMTP protocol */
lmtp_loop(lmtp);
return 0;
}
static void resetsession(struct session *session)
{
//FIXME what about non-graceful aborts?
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 void resetmessage(struct message *message)
{
//FIXME what about non-graceful aborts?
if (message->mail_from != NULL)
free(message->mail_from);
if (message->azRcpt != NULL)
free(message->azRcpt);
if (message->azHeaders != NULL)
free(message->azHeaders);
if (message->cpMsg != NULL)
free(message->cpMsg);
if (message->cpHeaders != NULL)
free(message->cpHeaders);
if (message->cpBody != NULL)
free(message->cpBody);
initmessage(message);
return;
}
static void initmessage(struct message *message)
{
message->mail_from = NULL;
message->azRcpt = NULL;
message->asRcpt = 0;
message->azHeaders = NULL;
message->asHeaders = 0;
message->cpMsg = NULL;
message->cpHeaders = NULL;
message->cpBody = NULL;
return;
}
static lmtp_rc_t lmtp_cb_lhlo(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx)
{
/*
* RFC821 [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;
if (ctx->session.lhlo_seen == TRUE) {
res.statuscode = "503";
res.dsncode = "5.0.0";
res.statusmsg = "Duplicate LHLO.";
lmtp_response(lmtp, &res);
return LMTP_OK;
}
if (! ( helo_rfc0821domain(req->msg, &ctx->session.lhlo_domain)
|| helo_rfc1035domain(req->msg, &ctx->session.lhlo_domain)
)) {
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;
}
if (ctx->nsc == 0) {
res.statuscode = "501";
res.dsncode = "5.0.0";
res.statusmsg = "No valid NNTP Services specified.";
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) {
fprintf(stderr, "DEBUG: connect failed: %s\n", strerror(errno));
bOk = FALSE;
}
if (bOk && ((ctx->ns[i].nntp = nntp_create(ctx->ns[i].s, ctx->ns[i].s, NULL)) == NULL)) {
fprintf(stderr, "DEBUG: nntp_create failed: %s\n", strerror(errno));
bOk = FALSE;
}
if (bOk && ((rc = nntp_init(ctx->ns[i].nntp)) != NNTP_OK)) {
fprintf(stderr, "DEBUG: nntp_init failed: %s\n", nntp_error(ctx->ns[i].nntp, rc));
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);
if (ctx->nsc == 0) {
res.statuscode = "501";
res.dsncode = "5.0.0";
res.statusmsg = "No connection to any NNTP Service."; //FIXME add error strings from above DEBUGs
lmtp_response(lmtp, &res);
return LMTP_OK;
}
ctx->session.lhlo_seen = TRUE;
str_format(str, sizeof(str),
"FIXME.dev.de.cw.net" /* RFC2821 4.1.1.1 */
" Hello %s, pleased to meet you.\n"
"ENHANCEDSTATUSCODES\n" /* RFC2034 */
"DSN\n" /* RFC1894 */
"PIPELINING\n" /* RFC1854 */
"8BITMIME", /* RFC1652 */
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 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 RFC 821:
# <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 =~ s|\\|\\\\|sg;
$cregex =~ s|(.{70})|"$1"\n|sg;
$cregex =~ s|\n([^\n]+)$|\n"$1"|s; #FIXME this fails when last
#FIXME line matches linelength exacly
print "$cregex\n";
*/
"(?:(?:[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 =~ s|\\|\\\\|sg;
$cregex =~ s|(.{70})|"$1"\n|sg;
$cregex =~ s|\n([^\n]+)$|\n"$1"|s; #FIXME this fails when last
#FIXME line matches linelength exacly
print "$cregex\n";
*/
"(?:(?:(?:[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)
{
lmtp_res_t res;
lmtp_rc_t rc = LMTP_OK;
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
if (ctx->session.lhlo_seen != TRUE) {
res.statuscode = "553";
res.dsncode = "5.1.8";
res.statusmsg = "friendly people say LHLO to open a transmission channel.";
lmtp_response(lmtp, &res);
}
else if (ctx->message.mail_from != NULL) {
res.statuscode = "503";
res.dsncode = "5.5.0";
res.statusmsg = "Sender already specified.";
lmtp_response(lmtp, &res);
}
else
if (!str_parse(req->msg, "m/^MAIL From: <(.+@.+)>$/i", &ctx->message.mail_from)) {
res.statuscode = "553";
res.dsncode = "5.5.4";
res.statusmsg = "Domain name required for sender address.";
lmtp_response(lmtp, &res);
}
else {
res.statuscode = "250";
res.dsncode = "2.1.0";
res.statusmsg = "Sender ok.";
lmtp_response(lmtp, &res);
}
return rc;
}
static lmtp_rc_t lmtp_cb_rcpt(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx)
{
lmtp_res_t res;
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
char *cp;
char *group;
if (ctx->message.mail_from == NULL) {
res.statuscode = "503";
res.dsncode = "5.0.0";
res.statusmsg = "specify sender with MAIL first.";
lmtp_response(lmtp, &res);
return LMTP_OK;
}
if (!str_parse(req->msg, "m/^RCPT To: (.+)$/i", &cp)) {
res.statuscode = "501";
res.dsncode = "5.5.2";
res.statusmsg = "Syntax error in parameters.";
lmtp_response(lmtp, &res);
return LMTP_OK;
}
/* FIXME
* 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.
*/
if (ctx->option_groupmode == GROUPMODE_ENVELOPE) {
// fprintf(stderr, "DEBUG: before transform cp=***%s***\n", cp);
if (!str_parse(cp, "m/^.+?\\+(.+)?@.+$/i", &group)) { //FIXME >=2 * @
res.statuscode = "550";
res.dsncode = "5.1.1";
res.statusmsg = "Recipient did not transform into Group.";
lmtp_response(lmtp, &res);
return LMTP_OK;
}
// fprintf(stderr, "DEBUG: after transform group=***%s***\n", group);
//FIXME do additional transform and checking
if (0) {
res.statuscode = "550";
res.dsncode = "5.1.1";
res.statusmsg = "unmatched Group.";
lmtp_response(lmtp, &res);
return LMTP_OK;
}
}
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;
}
fprintf(stderr, "DEBUG: cp=***%s***\n", cp);
argz_add(&ctx->message.azRcpt, &ctx->message.asRcpt, cp);
argz_add(&ctx->azGroups, &ctx->asGroups, group);
res.statuscode = "250";
res.dsncode = "2.1.5";
res.statusmsg = "Recipient/ Group accepted";
lmtp_response(lmtp, &res);
return LMTP_OK;
}
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 errorstring[STDSTRLEN];
char *rcpt;
if (argz_count(ctx->message.azRcpt, ctx->message.asRcpt) == 0) {
res.statuscode = "503";
res.dsncode = "5.0.0";
res.statusmsg = "specify recipient with RCPT first.";
lmtp_response(lmtp, &res);
return LMTP_OK;
}
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->message.cpMsg, MESSAGE_MAXLEN);
if (rc == LMTP_ERR_OVERFLOW) {
rcpt = NULL;
while ((rcpt = argz_next(ctx->message.azRcpt, ctx->message.asRcpt, rcpt)) != NULL) {
res.statuscode = "500";
res.dsncode = "5.0.0";
res.statusmsg = "Overflow reading message"; //FIXME temp or perm error?
lmtp_response(lmtp, &res);
return LMTP_OK;
}
}
if (rc == LMTP_ERR_SYSTEM) {
rcpt = NULL;
while ((rcpt = argz_next(ctx->message.azRcpt, ctx->message.asRcpt, rcpt)) != NULL) {
res.statuscode = "500";
res.dsncode = "5.0.0";
str_format(errorstring, sizeof(errorstring), "System error reading message: %s", strerror(errno));
res.statusmsg = errorstring;
lmtp_response(lmtp, &res);
return LMTP_OK;
}
}
if(rc != LMTP_OK) {
rcpt = NULL;
while ((rcpt = argz_next(ctx->message.azRcpt, ctx->message.asRcpt, rcpt)) != NULL) {
res.statuscode = "500";
res.dsncode = "5.0.0";
res.statusmsg = "Unknown error reading message"; //FIXME call lmtp_error()?
lmtp_response(lmtp, &res);
return LMTP_OK;
}
}
/* manipulate headers
* - remove To: and Cc: headers
* - (re)create Newsgroups: header
*/
prepareheader(ctx);
rcpt = NULL;
while ((rcpt = argz_next(ctx->message.azRcpt, ctx->message.asRcpt, rcpt)) != NULL) {
res.statuscode = "250";
res.dsncode = "2.0.0";
str_format(errorstring, sizeof(errorstring), "Message accepted for delivery to %s", rcpt);
res.statusmsg = errorstring;
lmtp_response(lmtp, &res);
}
resetmessage(&ctx->message);
return rc;
}
lmtp_rc_t prepareheader(void *_ctx)
{
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
char *cpName;
char *cpValue;
char *cpRem;
char *cp;
char *azNewsgroups;
int asNewsgroups;
char **aHeaders;
int i;
char *cpCut;
char *cpWrap;
/****************
* PARSE HEADER * FIXME make it nice
****************/
if (!str_parse(ctx->message.cpMsg, "m/((?:.*?)\\n)\\n(.*)$/s", &ctx->message.cpHeaders, &ctx->message.cpBody))
return LMTP_ERR_ARG;
if (strlen(ctx->message.cpHeaders) < 4 + 1)
return LMTP_ERR_ARG;
memcpy(ctx->message.cpHeaders, "X-F:", 4); /* replace envelope From w/o colon by X-F: pseudotag */
// fprintf(stderr, "DEBUG: input ***%s***\n", ctx->message.cpHeaders);
while (str_parse(ctx->message.cpHeaders, "s/(.*?)\\n[ \\t]+(.*)/$1 $2/s", &cpRem)) { //FIXME poor man's s///g simulator
free(ctx->message.cpHeaders);
ctx->message.cpHeaders = cpRem;
}
/*
* FIXME str enhancement requests and bugs to be fixed
*
* - fix bug "not" [^...] working
* - improve str_parse(foo, "...", &foo) should free foo() on it's own
* - add "global" in s/search/replace/g
*/
// fprintf(stderr, "DEBUG: unwrapped ***%s***\n", ctx->message.cpHeaders);
while (str_parse(ctx->message.cpHeaders, "m/^([\\w-]+?:)\\s*([\\S \\t]*?)\\n(.*)/s", &cpName, &cpValue, &cpRem)) {
free(ctx->message.cpHeaders);
ctx->message.cpHeaders = cpRem;
//fprintf(stderr, "DEBUG: raw Name(%s) = Value(%s)\n", cpName, cpValue);
argz_add(&ctx->message.azHeaders, &ctx->message.asHeaders, cpName);
argz_add(&ctx->message.azHeaders, &ctx->message.asHeaders, cpValue);
}
memcpy(ctx->message.azHeaders, "From", 4); /* replace envelope X-F: pseudotag with From w/o colon */
// fprintf(stderr, "DEBUG: remainder ***%s***\n", ctx->message.cpHeaders);
// fprintf(stderr, "DEBUG: body ***%s***\n", ctx->message.cpBody);
/*********************
* MANIPULATE HEADER * FIXME make it nice
*********************/
/* throw out headers we don't want anymore */
cp = NULL;
while ((cp = argz_next(ctx->message.azHeaders, ctx->message.asHeaders, cp)) != NULL) {
if (strcasecmp("To:", cp) == 0) {
argz_delete(&ctx->message.azHeaders, &ctx->message.asHeaders, cp); /* name */
argz_delete(&ctx->message.azHeaders, &ctx->message.asHeaders, cp); /* value */
} //FIXME what bad things can happen here (odd number of argz, delete fails ...
if (strcasecmp("Cc:", cp) == 0) {
argz_delete(&ctx->message.azHeaders, &ctx->message.asHeaders, cp); /* name */
argz_delete(&ctx->message.azHeaders, &ctx->message.asHeaders, cp); /* value */
} //FIXME what bad things can happen here (odd number of argz, delete fails ...
}
/* create a proper Newsgroups: header */
cp = NULL;
azNewsgroups = NULL;
asNewsgroups = 0;
while ((cp = argz_next(ctx->azGroups, ctx->asGroups, cp)) != NULL) {
// fprintf(stderr, "DEBUG: Group(%s)\n", cp);
argz_add(&azNewsgroups, &asNewsgroups, cp);
}
argz_stringify(azNewsgroups, asNewsgroups, ',');
argz_add(&ctx->message.azHeaders, &ctx->message.asHeaders, "Newsgroups:");
argz_add(&ctx->message.azHeaders, &ctx->message.asHeaders, azNewsgroups);
/* merge name/value pairs into single string */
argz_add(&ctx->message.azHeaders, &ctx->message.asHeaders, ""); /* append empty string */
if ((aHeaders = (char **)malloc((argz_count(ctx->message.azHeaders,
ctx->message.asHeaders) + 1) * sizeof(char *))) == NULL)
exit(1); //FIXME
argz_extract(ctx->message.azHeaders, ctx->message.asHeaders, aHeaders);
i=0;
while(1) {
if ((cp = aHeaders[++i]) == NULL)
break;
*(cp-1) = ' ';
if ((cp = aHeaders[++i]) == NULL)
break;
// *(cp-1) = '\n';
}
/* fold headers */
//FIXME where to place this defines best
#define WRAPAT 120
#define WRAPUSING "\n "
cp = NULL;
while ((cp = argz_next(ctx->message.azHeaders, ctx->message.asHeaders, cp)) != NULL) {
if (strlen(cp) >= WRAPAT) {
cpRem = cp;
cpWrap = NULL;
while (strlen(cpRem) >= WRAPAT) {
for (i = WRAPAT; i >= 1 && (cpRem[i] != ' ') && (cpRem[i] != '\t'); i--);
if (i == 0)
i = WRAPAT; /* sorry, hard cut at non-whitespace */
if (i < WRAPAT)
i++; /* we don't care about the whitespace itself */
cpCut = str_dup(cpRem, i);
if (cpWrap == NULL) {
if ((cpWrap = (char *)malloc(strlen(cpCut)+strlen(WRAPUSING)+1)) == NULL)
exit(1); //FIXME
*cpWrap = '\0';
}
else {
if ((cpWrap = (char *)realloc(cpWrap, strlen(cpWrap)+strlen(cpCut)+strlen(WRAPUSING)+1)) == NULL)
exit(1); //FIXME
strcat(cpWrap, WRAPUSING);
}
strcat(cpWrap, cpCut);
free(cpCut);
cpRem += i;
}
for (i = 0; i < strlen(cpRem) && ((cpRem[i] == ' ') || (cpRem[i] == '\t')); i++);
cpRem += i;
if (strlen(cpRem) > 0) {
if ((cpWrap = (char *)realloc(cpWrap, strlen(cpWrap)+strlen(cpRem)+strlen(WRAPUSING)+1)) == NULL)
exit(1); //FIXME
strcat(cpWrap, WRAPUSING);
strcat(cpWrap, cpRem);
}
argz_delete(&ctx->message.azHeaders, &ctx->message.asHeaders, cp);
argz_insert(&ctx->message.azHeaders, &ctx->message.asHeaders, cp, cpWrap);
free(cpWrap);
//fprintf(stderr, "DEBUG: after wrap = ***%s***\n", cp);
}
}
argz_stringify(ctx->message.azHeaders, ctx->message.asHeaders, '\n');
fprintf(stderr, "DEBUG: flat headers = ***%s***\n", ctx->message.azHeaders);
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;
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;
resetmessage(&ctx->message);
res.statuscode = "250";
res.dsncode = "2.0.0";
res.statusmsg = "Reset state.";
lmtp_response(lmtp, &res);
return rc;
}
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;
resetmessage(&ctx->message);
resetsession(&ctx->session);
res.statuscode = "221";
res.dsncode = "2.0.0";
res.statusmsg = "LMTP Service closing transmission channel.";
lmtp_response(lmtp, &res);
return rc;
}