Index: ossp-pkg/lmtp2nntp/00TODO RCS File: /v/ossp/cvs/ossp-pkg/lmtp2nntp/00TODO,v rcsdiff -q -kk '-r1.9' '-r1.10' -u '/v/ossp/cvs/ossp-pkg/lmtp2nntp/00TODO,v' 2>/dev/null --- 00TODO 2001/08/07 09:05:06 1.9 +++ 00TODO 2001/08/16 15:00:50 1.10 @@ -1,4 +1,16 @@ +openpkg +test auf mail.de.cw.net +whatsup draufleiten +dmalloc +autoconf +FIXMEs +// +DEBUGs +RFC1891 ENVID +RFC1891 6.2 +FALSE, TRUE vs. == FALSE, == TRUE, != FALSE, != TRUE + **** LMTP REDESIGN **** RESULT: Index: ossp-pkg/lmtp2nntp/lmtp.c RCS File: /v/ossp/cvs/ossp-pkg/lmtp2nntp/Attic/lmtp.c,v rcsdiff -q -kk '-r1.10' '-r1.11' -u '/v/ossp/cvs/ossp-pkg/lmtp2nntp/Attic/lmtp.c,v' 2>/dev/null --- lmtp.c 2001/08/14 14:42:41 1.10 +++ lmtp.c 2001/08/16 15:00:50 1.11 @@ -142,6 +142,7 @@ lmtp_rc_t lmtp_readline(lmtp_t *lmtp, char *buf, size_t buflen) { /* read a line (characters until NL) from input stream */ + //FIXME RFC0821 4.2. SMTP REPLIES "Only the EXPN and HELP commands are expected to result in multiline replies in normal circumstances, however multiline replies are allowed for any command." size_t n; char c; lmtp_readline_t *rl = &lmtp->rl; Index: ossp-pkg/lmtp2nntp/lmtp2nntp.c RCS File: /v/ossp/cvs/ossp-pkg/lmtp2nntp/Attic/lmtp2nntp.c,v rcsdiff -q -kk '-r1.17' '-r1.18' -u '/v/ossp/cvs/ossp-pkg/lmtp2nntp/Attic/lmtp2nntp.c,v' 2>/dev/null --- lmtp2nntp.c 2001/08/14 14:42:41 1.17 +++ lmtp2nntp.c 2001/08/16 15:00:50 1.18 @@ -74,6 +74,9 @@ int option_verbose; int option_tracing; int option_groupmode; + int option_deliverymode; + char *option_deliverymodefakestatus; + char *option_deliverymodefakedsn; int nsc; struct ns ns[MAXNEWSSERVICES]; char *azGroupargs; @@ -88,6 +91,12 @@ GROUPMODE_HEADER }; +enum { + DELIVERYMODE_FAKE, + DELIVERYMODE_POST, + DELIVERYMODE_FEED +}; + /* * print usage information */ @@ -130,6 +139,9 @@ 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->nsc = 0; for (i=0; i < MAXNEWSSERVICES; i++) { ctx->ns[i].h = ""; @@ -157,8 +169,48 @@ #endif /* read in the arguments */ - while ((i = getopt(argc, argv, "g:h:t:v")) != -1) { + while ((i = getopt(argc, argv, "d:g:h:t:v")) != -1) { switch (i) { + case 'd': /* -d 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] = '\0'; + ctx->option_deliverymodefakestatus = &optarg[0]; + ctx->option_deliverymodefakedsn = &optarg[4]; + + if ( strlen(ctx->option_deliverymodefakestatus) != 3 + || !isdigit(ctx->option_deliverymodefakestatus[0]) + || !isdigit(ctx->option_deliverymodefakestatus[1]) + || !isdigit(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(ctx->option_deliverymodefakedsn[0]) + || (ctx->option_deliverymodefakedsn[1] != '.') + || !isdigit(ctx->option_deliverymodefakedsn[2]) + || (ctx->option_deliverymodefakedsn[3] != '.') + || !isdigit(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': /* -g groupmode */ if (strcasecmp(optarg, "arg") == 0) ctx->option_groupmode = GROUPMODE_ARG; @@ -355,9 +407,17 @@ } } while (i < ctx->nsc); + /* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS + * "421 Service not available, closing transmission channel [This + * may be a reply to any command if the service knows it must shut down]" + * + * RFC1893 2. Status Codes, 3.5 Network and Routing Status + * 4.X.X Persistent Transient Failure + * X.4.1 No answer from host + */ if (ctx->nsc == 0) { - res.statuscode = "501"; - res.dsncode = "5.0.0"; + res.statuscode = "421"; + res.dsncode = "4.4.1"; res.statusmsg = "No connection to any NNTP Service."; //FIXME add error strings from above DEBUGs lmtp_response(lmtp, &res); return LMTP_OK; @@ -715,26 +775,72 @@ } //fprintf(stderr, "DEBUG: after msg_join\n"); - bSuccess = FALSE; + bSuccess = NNTP_EOF; /* assume a hard error for the worst case */ for (i = 0; i < ctx->nsc; i++) { //fprintf(stderr, "DEBUG: trying service %s\n", ctx->ns[i].h); - if ((rc = nntp_post(ctx->ns[i].nntp, ctx->msg)) == NNTP_OK) - bSuccess = TRUE; + switch (ctx->option_deliverymode) { + case DELIVERYMODE_FAKE: + break; + case DELIVERYMODE_POST: + rc = nntp_post(ctx->ns[i].nntp, ctx->msg); + break; + case DELIVERYMODE_FEED: + rc = nntp_feed(ctx->ns[i].nntp, ctx->msg); + break; + } + if (rc == NNTP_OK) + bSuccess = NNTP_OK; + if ( bSuccess != NNTP_OK + && ( + (rc == NNTP_TIMEOUT) + || (rc == NNTP_ERR_SYSTEM) + || (rc == NNTP_DEFER) + ) + ) + bSuccess = NNTP_DEFER; } //FIXME rc has only last error + /* 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 (bSuccess == TRUE) { - str_format(errorstring, sizeof(errorstring), "Message accepted for delivery to %s", rcpt); - res.statuscode = "250"; - res.dsncode = "2.0.0"; - res.statusmsg = errorstring; + if (ctx->option_deliverymode == DELIVERYMODE_FAKE) { + res.statuscode = ctx->option_deliverymodefakestatus; + res.dsncode = ctx->option_deliverymodefakedsn; + res.statusmsg = "NNTP noop fake return"; } else { - str_format(errorstring, sizeof(errorstring), "Last error posting message: %s", nntp_error(rc)); - res.statuscode = "500"; - res.dsncode = "5.0.0"; - res.statusmsg = errorstring; + 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"; + res.statusmsg = errorstring; + break; + case NNTP_DEFER: + res.statuscode = "451"; + res.dsncode = "4.4.2"; + res.statusmsg = "Requested action aborted, local error in processing."; + lmtp_response(lmtp, &res); + break; + default: + str_format(errorstring, sizeof(errorstring), "Last error posting message: %s", nntp_error(rc)); + res.statuscode = "554"; + res.dsncode = "5.4.2"; + res.statusmsg = errorstring; + } } lmtp_response(lmtp, &res); } Index: ossp-pkg/lmtp2nntp/lmtp2nntp.pod RCS File: /v/ossp/cvs/ossp-pkg/lmtp2nntp/lmtp2nntp.pod,v rcsdiff -q -kk '-r1.5' '-r1.6' -u '/v/ossp/cvs/ossp-pkg/lmtp2nntp/lmtp2nntp.pod,v' 2>/dev/null --- lmtp2nntp.pod 2001/08/14 08:15:25 1.5 +++ lmtp2nntp.pod 2001/08/16 15:00:50 1.6 @@ -15,6 +15,7 @@ [B<-p> I] [B<-l> I] [B<-h> I[I<:port>]] +[B<-d> I] [B<-g> I] [B<-t> I] [B<-v>] @@ -76,6 +77,19 @@ up distribution by posting the same article to more than one server. In regard to this program they must provide the same groups and talk to each other. +=item B<-d> I + +Possible values for I are C, C or a string used to +fake a LMTP return code and DSN in "LLL/D.D.D" format. The slash is replaced +by a space internally. The default is "553/5.7.1" meaning "Requested action +not taken: mailbox name not allowed/ Delivery not authorized, message +refused". In C mode articles are sent to the NNTP server(s) using POST +command. Before posting, a duplicate check using STAT command is issued. In +C mode articles are sent to the NNTP server(s) using IHAVE command. +Specifying a return code/ DSN replaces the post/ feed logic by a noop and +assumes the given string must be returned to the LMTP side. This is useful for +debugging LMTP setups without engaging NNTP. + =item B<-g> I Possible values for I are C (default), C and Index: ossp-pkg/lmtp2nntp/msg.c RCS File: /v/ossp/cvs/ossp-pkg/lmtp2nntp/Attic/msg.c,v rcsdiff -q -kk '-r1.4' '-r1.5' -u '/v/ossp/cvs/ossp-pkg/lmtp2nntp/Attic/msg.c,v' 2>/dev/null --- msg.c 2001/08/14 14:42:41 1.4 +++ msg.c 2001/08/16 15:00:50 1.5 @@ -74,9 +74,9 @@ * msg->azHeaders, msg->asHeaders contains the headers in argz format, one * logical '\0'-terminated line per header which might be wrapped into * multiple '\n'-ended physical lines. The "From " envelope, "Received:", - * "To:" and "Cc:" headers are removed silently. The "Newsgroups:" and - * "Message-ID" headers are removed and their values are stored in - * separate structures (see below). + * "Path:", "To:" and "Cc:" headers are removed silently. The + * "Newsgroups:" and "Message-ID" headers are removed and their values are + * stored in separate structures (see below). * * msg->cpBody * contains the unmodified body of the message, '\0'-terminated, no @@ -132,6 +132,11 @@ argz_delete(&msg->azHeaders, &msg->asHeaders, cp); /* del value */ continue; } + if (strcasecmp("Path:", cp) == 0) { + argz_delete(&msg->azHeaders, &msg->asHeaders, cp); /* del name */ + argz_delete(&msg->azHeaders, &msg->asHeaders, cp); /* del value */ + continue; + } if (strcasecmp("Received:", cp) == 0) { argz_delete(&msg->azHeaders, &msg->asHeaders, cp); /* del name */ argz_delete(&msg->azHeaders, &msg->asHeaders, cp); /* del value */ @@ -180,6 +185,8 @@ if (argz_create_sep(msg->azNewsgroups, ',', &msg->azNewsgroups, &msg->asNewsgroups) != 0) return MSG_ERR_MEM; } + argz_add(&msg->azHeaders, &msg->asHeaders, "Path:"); + argz_add(&msg->azHeaders, &msg->asHeaders, "not-for-mail"); //FIXME return MSG_OK; } Index: ossp-pkg/lmtp2nntp/nntp.c RCS File: /v/ossp/cvs/ossp-pkg/lmtp2nntp/Attic/nntp.c,v rcsdiff -q -kk '-r1.7' '-r1.8' -u '/v/ossp/cvs/ossp-pkg/lmtp2nntp/Attic/nntp.c,v' 2>/dev/null --- nntp.c 2001/08/14 14:42:41 1.7 +++ nntp.c 2001/08/16 15:00:50 1.8 @@ -37,6 +37,14 @@ #include #include "nntp.h" +#include "str.h" + +#ifndef FALSE +#define FALSE (1 != 1) +#endif +#ifndef TRUE +#define TRUE (!FALSE) +#endif /* maximum NNTP protocol line length */ #define NNTP_LINE_MAXLEN 1024 @@ -53,6 +61,7 @@ nntp_io_t io; nntp_readline_t rl; struct timeval tv; + int kludgeinn441dup; }; nntp_t *nntp_create(int rfd, int wfd, nntp_io_t *io) @@ -78,6 +87,7 @@ nntp->rl.rl_bufptr = NULL; nntp->rl.rl_buf[0] = '\0'; nntp_timeout(nntp, 3); + nntp->kludgeinn441dup = FALSE; return nntp; } @@ -121,6 +131,19 @@ //fprintf(stderr, "DEBUG: nntp_readline got ***%s***\n", line); + /* prepare for the INN kludge using 441 returns for other things but + * "Duplicate". Typical welcome string is "200 host InterNetNews NNRP + * server INN 2.3.2 ready (posting * ok)." + */ + if (str_parse(line, "m/^200.*\\sINN\\s/")) { + nntp->kludgeinn441dup = TRUE; + //fprintf(stderr, "DEBUG: INN kludge activated!\n"); + } + else { + nntp->kludgeinn441dup = FALSE; + //fprintf(stderr, "DEBUG: no INN kludge!\n"); + } + if (strncmp(line, "200", 3) == 0) return NNTP_OK; @@ -231,14 +254,12 @@ * * In other words, INN *requires* the use of "MODE READER". */ - //fprintf(stderr, "DEBUG: before MODE READER\n"); *line = '\0'; strcat(line, "MODE READER"); if ((rc = nntp_writeline(nntp, line)) != NNTP_OK) return rc; if ((rc = nntp_readline(nntp, line, sizeof(line))) != NNTP_OK) return rc; - //fprintf(stderr, "DEBUG: after MODE READER\n"); /* A 200 response means posting ok, 201 means posting not allowed. We * don't care about 5xx errors, they simply mean the server doesn't know @@ -303,8 +324,6 @@ return rc; if ((rc = nntp_readline(nntp, line, sizeof(line))) != NNTP_OK) return rc; - if (strncmp(line, "xxx", 3) == 0) - return NNTP_OK; if (strncmp(line, "340", 3) != 0) return NNTP_ERR_POST; @@ -314,10 +333,19 @@ if ((rc = nntp_readline(nntp, line, sizeof(line))) != NNTP_OK) return rc; //fprintf(stderr, "DEBUG: answer to post = ***%s***, rc = %s\n", line, nntp_error(rc)); - if ( (strncmp(line, "240", 3) == 0) - || (strncmp(line, "441", 3) == 0) - ) + + if (strncmp(line, "240", 3) == 0) return NNTP_OK; + + if (nntp->kludgeinn441dup == TRUE) { + if (strncmp(line, "441 435", 7) == 0) + return NNTP_OK; + } + else { + if (strncmp(line, "441", 3) == 0) + return NNTP_OK; + } + return NNTP_ERR_POST; #if 0 @@ -330,6 +358,64 @@ #endif } +nntp_rc_t nntp_feed(nntp_t *nntp, msg_t *msg) +{ + nntp_rc_t rc = NNTP_OK; + char line[NNTP_LINE_MAXLEN]; + + /* RFC0977 3.4. The IHAVE command, 3.4.2. Responses + * < 235 article transferred ok + * < 335 send article to be transferred. End with . + * < 435 article not wanted - do not send it + * < 436 transfer failed - try again later + * < 437 article rejected - do not try again + * + * Research: + * < 200 dev16 InterNetNews server INN 2.3.2 ready + * > IHAVE + * < 335 [no text; this number means positive response] + * < 435 Duplicate + * < 437 Missing "Path" header + * < 437 Unwanted newsgroup "quux" + * < 480 Transfer permission denied + */ + *line = '\0'; + strcat(line, "IHAVE "); + strcat(line, msg->cpMsgid); + if ((rc = nntp_writeline(nntp, line)) != NNTP_OK) + return rc; + + if ((rc = nntp_readline(nntp, line, sizeof(line))) != NNTP_OK) + return rc; + + if (strncmp(line, "435", 3) == 0) + return NNTP_OK; + + if (strncmp(line, "436", 3) == 0) + return NNTP_DEFER; + + if ( (strncmp(line, "437", 3) == 0) //FIXME style vs. optimization - redundant lines + || (strncmp(line, "480", 3) == 0) + || (strncmp(line, "335", 3) != 0) + ) + return NNTP_ERR_POST; + + if ((rc = nntp->io.write(nntp->wfd, msg->cpMsg, strlen(msg->cpMsg))) < 0) //FIXME while() wrapper around write required + return NNTP_ERR_SYSTEM; + + if ((rc = nntp_readline(nntp, line, sizeof(line))) != NNTP_OK) + return rc; + //fprintf(stderr, "DEBUG: answer to post = ***%s***, rc = %s\n", line, nntp_error(rc)); + + if (strncmp(line, "235", 3) == 0) + return NNTP_OK; + + if (strncmp(line, "436", 3) == 0) + return NNTP_DEFER; + + return NNTP_ERR_POST; +} + char *nntp_error(nntp_rc_t rc) { char *str; @@ -337,10 +423,12 @@ if (rc == NNTP_OK ) str = "NNTP: no error"; else if (rc == NNTP_EOF ) str = "NNTP: end of file"; else if (rc == NNTP_TIMEOUT ) str = "NNTP: timeout"; + else if (rc == NNTP_DEFER ) str = "NNTP: transmission deferred"; else if (rc == NNTP_ERR_SYSTEM ) str = "NNTP: see errno"; else if (rc == NNTP_ERR_ARG ) str = "NNTP: invalid argument"; else if (rc == NNTP_ERR_OVERFLOW) str = "NNTP: buffer overflow"; else if (rc == NNTP_ERR_POST ) str = "NNTP: cannot post message"; + else if (rc == NNTP_ERR_INIT ) str = "NNTP: initialization failed"; return str; } Index: ossp-pkg/lmtp2nntp/nntp.h RCS File: /v/ossp/cvs/ossp-pkg/lmtp2nntp/Attic/nntp.h,v rcsdiff -q -kk '-r1.5' '-r1.6' -u '/v/ossp/cvs/ossp-pkg/lmtp2nntp/Attic/nntp.h,v' 2>/dev/null --- nntp.h 2001/08/14 14:42:41 1.5 +++ nntp.h 2001/08/16 15:00:50 1.6 @@ -49,11 +49,11 @@ NNTP_OK, NNTP_EOF, NNTP_TIMEOUT, + NNTP_DEFER, NNTP_ERR_SYSTEM, NNTP_ERR_ARG, NNTP_ERR_OVERFLOW, NNTP_ERR_POST, - NNTP_ERR_POSTDEFER, NNTP_ERR_INIT } nntp_rc_t; @@ -64,6 +64,7 @@ nntp_rc_t nntp_readline (nntp_t *, char *, size_t); nntp_rc_t nntp_writeline(nntp_t *, char *); nntp_rc_t nntp_post (nntp_t *, msg_t *msg); +nntp_rc_t nntp_feed (nntp_t *, msg_t *msg); char *nntp_error (nntp_rc_t); #endif /* __NNTP_H__ */ Index: ossp-pkg/lmtp2nntp/test/run.sh RCS File: /v/ossp/cvs/ossp-pkg/lmtp2nntp/test/Attic/run.sh,v rcsdiff -q -kk '-r1.5' '-r1.6' -u '/v/ossp/cvs/ossp-pkg/lmtp2nntp/test/Attic/run.sh,v' 2>/dev/null --- run.sh 2001/08/14 14:42:41 1.5 +++ run.sh 2001/08/16 15:00:50 1.6 @@ -1,7 +1,17 @@ #/bin/sh +# -h news-muc1.de.cw.net +# -h news-ffm2.de.cw.net +# -h news-dus2.de.cw.net +# -h news.ecrc.de +# -h dev16 + before () { + #set -v -x + h="H" + z="Z" + e="E" if [ -e /tmp/tracing ] then ssh -t -x root@localhost exec rm -f /tmp/tracing @@ -13,53 +23,77 @@ { for h in 0 1 2 3 4 5 6 7 8 9 do - for z in 0 1 2 3 4 5 6 7 8 9 + for z in 0 1 2 3 4 5 6 7 8 9 do for e in 0 1 2 3 4 5 6 7 8 9 do echo -n $h$z$e - awk \" } \ - !/Message-ID/ { print \$0 } \ - " \ + newmsg sendmail + cat /tmp/testmessage \ | sendmail -i posting+$h$z$e@dev12.dev.de.cw.net done done done } -postdup () +lmtp() { - echo ----------------------------------------------------------- -#./lmtp2nntp -h news-muc1.de.cw.net -h news-ffm2.de.cw.net -h news-dus2.de.cw.net -h news-ecrc.de -#clear ; ./lmtp2nntp \" } \ - !/Message-ID/ { print \$0 } \ - " \ - >/tmp/testmessage - echo ----------------------------------------------------------- + newmsg lmtp cat /tmp/testmessage \ - | sendmail -i posting+x@dev12.dev.de.cw.net - echo ----------------------------------------------------------- + | ./lmtp2nntp -t /tmp/tracing -d $1 -g $2 -h dev16 cw.de.sd.apps.dev.test cat /tmp/testmessage \ - | sendmail -i posting+x@dev12.dev.de.cw.net - echo ----------------------------------------------------------- + | ./lmtp2nntp -t /tmp/tracing -d $1 -g $2 -h dev16 cw.de.sd.apps.dev.test } +sendmaildup() +{ + newmsg sendmail + cat /tmp/testmessage \ + | sendmail -i posting+$h$z$e@dev12.dev.de.cw.net + cat /tmp/testmessage \ + | sendmail -i posting+$h$z$e@dev12.dev.de.cw.net +} + +newmsg() +{ + if [ -r testmessage.via$1 ] + then + awk " } + /^Subject:/ { gotit = 1; print "Subject: [" pid "] " $2 } + { if (gotit == 0) { print }; gotit = 0 } + ' | tee /tmp/testmessage + else + echo "ERROR: file testmessage.via$1 not readable!" + exit 1 + fi + echo ----------------------------------------------------------- +} after () { if [ -r /tmp/tracing ] then - echo tail -f /tmp/tracing - tail -f /tmp/tracing + echo more /tmp/tracing + #tail -f /tmp/tracing fi } before -post1000 + +#ok newmsg sendmail; ls -l /tmp/testmessage && cat /tmp/testmessage +#ok lmtp feed arg +#ok lmtp post arg +#ok lmtp feed envelope +#ok lmtp post envelope +sendmaildup +#post1000 + after exit 0 Index: ossp-pkg/lmtp2nntp/test/testmessage.viasendmail RCS File: /v/ossp/cvs/ossp-pkg/lmtp2nntp/test/Attic/testmessage.viasendmail,v rcsdiff -q -kk '-r1.1' '-r1.2' -u '/v/ossp/cvs/ossp-pkg/lmtp2nntp/test/Attic/testmessage.viasendmail,v' 2>/dev/null --- testmessage.viasendmail 2001/08/14 14:43:27 1.1 +++ testmessage.viasendmail 2001/08/16 15:00:51 1.2 @@ -1,6 +1,7 @@ From: Thomas Lotterer Subject: lmtp2nntp testmessage.viasendmail Message-ID: +Newsgroups: cw.de.sd.apps.dev.test . a dot above