OSSP CVS Repository

ossp - ossp-pkg/lmtp2nntp/lmtp2nntp.c 1.11
Not logged in
[Honeypot]  [Browse]  [Directory]  [Home]  [Login
[Reports]  [Search]  [Ticket]  [Timeline
  [Raw

ossp-pkg/lmtp2nntp/lmtp2nntp.c 1.11
/*
 * 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;

                                /****************
                                 * 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
                                 *********************/

    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 ...
    }

    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);

    cp = NULL;
    while ((cp = argz_next(ctx->message.azHeaders, ctx->message.asHeaders, cp)) != NULL) {
        cpName = cp;
        cp = argz_next(ctx->message.azHeaders, ctx->message.asHeaders, cp);
        cpValue = cp;
        fprintf(stderr, "DEBUG: cooked Name(%s) = Value(%s)\n", cpName, cpValue);
    }

    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;
}


CVSTrac 2.0.1