OSSP CVS Repository

ossp - Check-in [742]
Not logged in
[Honeypot]  [Browse]  [Home]  [Login]  [Reports
[Search]  [Ticket]  [Timeline
  [Patchset]  [Tagging/Branching

Check-in Number: 742
Date: 2001-Aug-23 09:55:53 (local)
2001-Aug-23 07:55:53 (UTC)
User:thl
Branch:
Comment: worked out all FIXMEs in lmtp2nntp.c; completely reviewed all lmtp_response() statuscodes and dsncodes especially distinguishing between "5xx Permanent Failure" and "4xx Persistent Transient Failure" conditions, documented every return code including reference to and excerpt from related RFCs; added graceful release of all resources; replaced hardcoded uname by uname(3) system call; added wildmat-style pattern matching for GROUPMODE_ENVELOPE and GROUPMODE_HEADER using shpat_match; added multiline responses for erroneous status after posting/feeding in lmtp_cb_data(); pushed down and updated 00TODO including relocation of pertinent information directly into source;
Tickets:
Inspections:
Files:
ossp-pkg/lmtp2nntp/lmtp2nntp.c      1.23 -> 1.24     367 inserted, 141 deleted

ossp-pkg/lmtp2nntp/lmtp2nntp.c 1.23 -> 1.24

--- lmtp2nntp.c  2001/08/21 08:56:41     1.23
+++ lmtp2nntp.c  2001/08/23 07:55:53     1.24
@@ -15,10 +15,12 @@
 #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 */
 #include "lmtp.h"
@@ -64,6 +66,7 @@
 
 static void initsession(struct session *session);
 static void resetsession(struct session *session);
+int groupmatch(char *, size_t, char *);
 
 struct ns {
     char *h;        /* host */
@@ -71,23 +74,29 @@
     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     nsc;
-    struct  ns ns[MAXNEWSSERVICES];
-    char   *azGroupargs;
-    size_t  asGroupargs;
-    struct  session session;
-    msg_t  *msg;
+    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;
+    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,
@@ -124,16 +133,6 @@
     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 */
@@ -147,14 +146,21 @@
     ctx->option_deliverymodefakedsn    = "5.7.1"; /* Delivery not authorized, message refused */
     ctx->nsc = 0;
     for (i=0; i < MAXNEWSSERVICES; i++) {
-        ctx->ns[i].h = "";
+        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) != 0) {
+        fprintf(stderr, "%s:Error: uname failed \"%s\"\n", progname, strerror(errno));
+        exit(ERR_EXECUTION);
+    }
 
 #if 1
     {
@@ -253,6 +259,7 @@
                             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", 
@@ -262,8 +269,6 @@
                             strerror(errno));
                     exit(ERR_EXECUTION);
                 }
-                ctx->ns[ctx->nsc].sa = sa;
-                /*FIXME sa_destroy(sa); */
                 ctx->ns[ctx->nsc].nntp = NULL;
                 ctx->nsc++;
                 break;
@@ -299,25 +304,43 @@
         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, "NOOP", lmtp_cb_noop, 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);
+
     return rc;
 }
 
 static void resetsession(struct session *session)
 {
-                                             /*FIXME what about non-graceful aborts? */
     if (session->lhlo_domain != NULL)
-        free(session->lhlo_domain);         
+        free(session->lhlo_domain);
     initsession(session);
     return;
 }
@@ -332,7 +355,7 @@
 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
+     *  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
@@ -363,6 +386,10 @@
     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 == TRUE) {
         res.statuscode = "503";
         res.dsncode    = "5.0.0";
@@ -371,9 +398,12 @@
         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)
-           || helo_rfc1035domain(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.";
@@ -381,10 +411,14 @@
         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 = "501";
-        res.dsncode    = "5.0.0";
-        res.statusmsg  = "No valid NNTP Services specified.";
+        res.statuscode = "451";
+        res.dsncode    = "4.3.5";
+        res.statusmsg  = "No valid NNTP services configured.";
         lmtp_response(lmtp, &res);
         return LMTP_OK;
     }
@@ -414,38 +448,56 @@
         }
     } while (i < ctx->nsc);
 
-    /*  RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS
-     *  "421 <domain> 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
+    /*  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  = "No connection to any NNTP Service."; /*FIXME add error strings from above DEBUGs */
+        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),
-               "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 */
+               "%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.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[ctx->nsc].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;
@@ -460,7 +512,7 @@
      ##  linear grammar, but noth both).
      ##
     
-     # BNF grammar for <domain> according to RFC 821:
+     # 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
@@ -489,11 +541,12 @@
      # 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|(.{70})|"$1"\n|sg;
-     $cregex =~ s|\n([^\n]+)$|\n"$1"|s; #FIXME this fails when last
-                                        #FIXME line matches linelength exacly
-     print "$cregex\n";
+     $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"
@@ -547,11 +600,12 @@
      # 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|(.{70})|"$1"\n|sg;
-     $cregex =~ s|\n([^\n]+)$|\n"$1"|s; #FIXME this fails when last
-                                        #FIXME line matches linelength exacly
-     print "$cregex\n";
+     $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]))?"
@@ -567,6 +621,10 @@
     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 != TRUE) {
         res.statuscode = "553";
         res.dsncode    = "5.1.8";
@@ -575,6 +633,10 @@
         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";
@@ -583,36 +645,48 @@
         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 = "503"; /* FIXME */
-        res.dsncode    = "5.5.0";
+        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"
+    /*  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")) {
         res.statuscode = "553";
-        res.dsncode    = "5.5.4";
+        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*){0,1}$/i", 
                              &ctx->msg->mail_from)) {
@@ -625,6 +699,10 @@
         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.";
@@ -639,16 +717,23 @@
     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->mail_from == NULL) {
         res.statuscode = "503";
-        res.dsncode    = "5.0.0";
+        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)) {
-        /*FIXME                          ^^^^ is this space required/ valid? Sendmail skips them! */
         res.statuscode = "501";
         res.dsncode    = "5.5.2";
         res.statusmsg  = "Syntax error in parameters.";
@@ -656,6 +741,10 @@
         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";
@@ -664,61 +753,87 @@
         return LMTP_OK;
     }
 
-    /* FIXME
-     * in GROUPMODE = ARG|HEADER recipient must be acknowledged and stored to
+    /* 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) {
-        /*fprintf(stderr, "DEBUG: before transform cp=***%s***\n", cp); */
         if (!str_parse(cp, "m/^<(.+)?@[^@]+>$/i", &group)) {
             res.statuscode = "550";
             res.dsncode    = "5.1.1";
-            res.statusmsg  = "Recipient did not transform into Group.";
+            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) {
+        if (groupmatch(ctx->azGroupargs, ctx->asGroupargs, group) != TRUE) {
             res.statuscode = "550";
-            res.dsncode    = "5.1.1";
+            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);
     }
-    
-    /*fprintf(stderr, "DEBUG: cp=***%s***\n", cp); */
     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  = "Recipient/ Group accepted";
+    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 (argz_count(ctx->msg->azRcpt, ctx->msg->asRcpt) == 0) {
         res.statuscode = "503";
-        res.dsncode    = "5.0.0";
+        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";
@@ -726,11 +841,15 @@
 
     rc = lmtp_readmsg(lmtp, &ctx->msg->cpMsg, MESSAGE_MAXLEN);
 
+    /*  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.3.4   Message too big for system
+     */
     if (rc == LMTP_ERR_OVERFLOW) {
         str_format(errorstring, sizeof(errorstring), "Overflow reading message: %s", lmtp_error(rc));
-        res.statuscode = "500";
-        res.dsncode    = "5.0.0";
-        res.statusmsg  = errorstring; /*FIXME temp or perm error? */
+        res.statuscode = "552";
+        res.dsncode    = "5.3.4";
+        res.statusmsg  = errorstring;
         rcpt = NULL;
         while ((rcpt = argz_next(ctx->msg->azRcpt, ctx->msg->asRcpt, rcpt)) != NULL) {
             lmtp_response(lmtp, &res);
@@ -738,10 +857,14 @@
         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 = "500";
-        res.dsncode    = "5.0.0";
+        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) {
@@ -750,10 +873,14 @@
         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 = "500";
-        res.dsncode    = "5.0.0";
+        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) {
@@ -762,11 +889,14 @@
         return LMTP_OK;
     }
 
-    /*fprintf(stderr, "DEBUG: before msg_split\n"); */
+    /*  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 = "500";
-        res.dsncode    = "5.0.0";
+        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) {
@@ -774,30 +904,86 @@
         }
         return LMTP_OK;
     }
-    /*fprintf(stderr, "DEBUG: after msg_split\n"); */
 
     if      (ctx->option_groupmode == GROUPMODE_ENVELOPE) {
-        ctx->msg->azNewsgroups = memcpy(malloc(ctx->msg->asEnvgroups + 1), ctx->msg->azEnvgroups, ctx->msg->asEnvgroups); /*FIXME strdup == NULL */
-        ctx->msg->asNewsgroups =        ctx->msg->asEnvgroups;
+        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) {
-        ctx->msg->azNewsgroups = memcpy(malloc(ctx->asGroupargs + 1),      ctx->azGroupargs,      ctx->asGroupargs);      /*FIXME strdup == NULL */
-        ctx->msg->asNewsgroups =        ctx->asGroupargs;
+        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) != TRUE) {
+                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;
+        }
     }
-    /* else keep                   == GROUPMODE_HEADERS */
 
-#if 0 /*FIXME debug paragraph */
+#if 0 /*DEBUG and LOGGING paragraph */
     rcpt = NULL;
     while ((rcpt = argz_next(ctx->msg->azNewsgroups, ctx->msg->asNewsgroups, rcpt)) != NULL) {
         fprintf(stderr, "DEBUG: commited group ***%s***\n", rcpt);
     }
 #endif
 
-    /*fprintf(stderr, "DEBUG: before msg_join\n"); */
+    /*  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 = "500";
-        res.dsncode    = "5.0.0";
+        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) {
@@ -814,67 +1000,83 @@
             case DELIVERYMODE_FAKE:
                 break;
             case DELIVERYMODE_POST:
-                rc = nntp_post(ctx->ns[i].nntp, ctx->msg);
+                ctx->ns[i].rc = nntp_post(ctx->ns[i].nntp, ctx->msg);
                 break;
             case DELIVERYMODE_FEED:
-                rc = nntp_feed(ctx->ns[i].nntp, ctx->msg);
+                ctx->ns[i].rc = nntp_feed(ctx->ns[i].nntp, ctx->msg);
                 break;
         }
-        if (rc == NNTP_OK)
+        if (ctx->ns[i].rc == NNTP_OK)
             bSuccess = NNTP_OK;
         if (   bSuccess != NNTP_OK
             && (
-                   (rc == NNTP_TIMEOUT)
-                || (rc == NNTP_ERR_SYSTEM)
-                || (rc == NNTP_DEFER)
+                   (ctx->ns[i].rc == NNTP_TIMEOUT)
+                || (ctx->ns[i].rc == NNTP_ERR_SYSTEM)
+                || (ctx->ns[i].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
+    /*  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;
-                    res.statusmsg  = "NNTP noop fake return";
+                    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);
+                    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:
+                    str_format(errorstring, sizeof(errorstring),
+                               "Requested action aborted for %s, local error in processing.", rcpt);
                     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));
+                    str_format(errorstring, sizeof(errorstring),
+                               "Error sending article for %s.", rcpt);
                     res.statuscode = "554";
                     res.dsncode    = "5.4.2";
-                    res.statusmsg  = errorstring;
+                    break;
             }
         }
-        lmtp_response(lmtp, &res);
+        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);
@@ -888,6 +1090,10 @@
     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.";
@@ -901,9 +1107,12 @@
     lmtp_res_t res;
     lmtp_rc_t rc = LMTP_OK;
 
-    msg_destroy(ctx->msg);
-    ctx->msg = NULL;
+    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.";
@@ -911,16 +1120,27 @@
     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;
 
-    msg_destroy(ctx->msg);
-    ctx->msg = NULL;
+    lmtp_gfs_quit(ctx);
 
-    resetsession(&ctx->session);
+    /*  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.";
@@ -928,3 +1148,9 @@
     return rc;
 }
 
+static void lmtp_gfs_quit(lmtp2nntp_t *ctx)
+{
+    /* graceful shutdown */
+    lmtp_gfs_rset(ctx);
+    resetsession(&ctx->session);
+}

CVSTrac 2.0.1