OSSP CVS Repository

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

ossp-pkg/lmtp2nntp/lmtp2nntp.c 1.6
/*
 * mail2nntp.c
 *
 * The mail2nntp program reads mail from stdin and posts it
 * to one or more newsgroups using NNTP. It delivers the mes-
 * sage immediately or fails.  If queuing is desired it can
 * operate as a LMTP server.
 *
 * 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"

#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

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

typedef struct {
    int     option_verbose;
    int     option_tracing;
    int     option_mode;
    char   *azNewsgroups;
    size_t  asNewsgroups;
    char   *rfc822message;
    int     lhlo_seen;
    char   *lhlo_domain;
    char   *azRcpt;
    size_t  asRcpt;
} lmtp2nntp_t;

enum {
    MODE_ONCE,
    MODE_MANY
};

/*
 * 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)
{
    int rc = 0;
    lmtp_t *lmtp;
    lmtp_io_t lmtp_io;
    lmtp2nntp_t *ctx;
    int i; /* general purpose scratch int, index ... */

    char *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_mode = MODE_MANY;
    ctx->azNewsgroups = NULL;
    ctx->asNewsgroups = 0;
    ctx->rfc822message = NULL;
    ctx->lhlo_seen = FALSE;
    ctx->lhlo_domain = "";
    ctx->azRcpt = NULL;
    ctx->asRcpt = 0;

    /* read in the arguments */
    {
        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);
        }
    }
    while ((i = getopt(argc, argv, "p:l:h:m:tvo:c:i:t:")) != -1) {
        switch (i) {
            case 'p': // -p protocol
                break;
            case 'l': // -l logtarget
                break;
            case 'o': // FIXME
                break;
            case 'c': // FIXME
                break;
            case 'i': // FIXME
                break;
            case 't': // FIXME
                break;
            case 'h': // -h host[:port]
                break;
            case 'm': // -m mode
                if (strcasecmp(argv[optind], "once") == 0)
                    ctx->option_mode = MODE_ONCE;
                else if (strcasecmp(argv[optind], "many") == 0)
                    ctx->option_mode = MODE_MANY;
                else {
                    fprintf(stderr, "%s:Error: Invalid mode \"%s\" to option -m\n", progname, argv[optind]);
                    exit(ERR_EXECUTION);
                }
                break;
            case 'd': // -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 newsgroup names */
    for (i = optind; i < argc; i++)
        argz_add(&ctx->azNewsgroups, &ctx->asNewsgroups, 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 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_rc_t rc = LMTP_OK;
    lmtp_res_t res;
    char errorstring[STDSTRLEN];
    char *cp;
    int FIXME;

    cp = NULL;
    if (ctx->lhlo_seen == TRUE) {
        res.statuscode = "503";
        res.dsncode    = "5.0.0";
        res.statusmsg  = "Duplicate LHLO.";
    } else {
        if (!str_parse(req->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;
             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])\\])"

            ")$", &cp, &ctx->lhlo_domain)) {
            res.statuscode = "501";
            res.dsncode    = "5.0.0";
            res.statusmsg  = "Please identify yourself. See <domain> BNF in RFC821.";
        } else {
            ctx->lhlo_seen = TRUE;
            printf("DEBUG: cp=***%s***, v1=***%s***\n", cp, ctx->lhlo_domain);
            str_format(errorstring, sizeof(errorstring), "Hello ***%s***", ctx->lhlo_domain);
            res.statuscode = "250";
            res.dsncode    = NULL; /* DSN not used for greeting */
            res.statusmsg  = "ENHANCEDSTATUSCODES\nDSN\nPIPELINING\n8BITMIME";
                /*
                 *  RFC2034 = EHANCEDSTATUSCODES
                 *  RFC1894 = DSN
                 *  RFC1854 = PIPELINING
                 *  RFC1652 = 8BITMIME
                 */
        }
    }
    lmtp_response(lmtp, &res);
    if (cp) free(cp);
    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->lhlo_seen == TRUE) {
        res.statuscode = "250";
        res.dsncode    = "2.1.0";
        res.statusmsg  = "Sender ok FIXME";
        lmtp_response(lmtp, &res);
    } else {
        res.statuscode = "553";
        res.dsncode    = "5.1.8";
        res.statusmsg  = "friendly people say LHLO to open a transmission channel";
        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_rc_t rc = LMTP_OK;
    lmtp_res_t res;
    lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;

    argz_add(&ctx->azRcpt, &ctx->asRcpt, req->msg);
    res.statuscode = "250";
    res.dsncode    = "2.1.5";
    res.statusmsg  = "Recipient ok FIXME";
    lmtp_response(lmtp, &res);

    return rc;
}


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 *buf;
    char errorstring[STDSTRLEN];
    char *rcpt;

    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, &buf, MESSAGE_MAXLEN);

    rcpt = NULL;
    while ((rcpt = argz_next(ctx->azRcpt, ctx->asRcpt, rcpt)) != NULL)
    {
        if(rc == LMTP_OK) {
            res.statuscode = "250";
            res.dsncode    = "2.0.0";
            // res.statusmsg  = "Message accepted for delivery";
            str_format(errorstring, sizeof(errorstring),
                       "Message accepted for delivery to %s", rcpt);
            res.statusmsg  = errorstring;
        } else if (rc == LMTP_ERR_OVERFLOW) {
            res.statuscode = "500";
            res.dsncode    = "5.0.0";
            res.statusmsg  = "Message accepted for delivery";
        } else if (rc == LMTP_ERR_SYSTEM) {
            res.statuscode = "500";
            res.dsncode    = "5.0.0";
            str_format(errorstring, sizeof(errorstring),
                       "Message accepted for delivery %s", strerror(errno));
            res.statusmsg  = errorstring;
        }
        lmtp_response(lmtp, &res);
    }
    return rc;
}

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

    if (ctx->azRcpt != NULL) {
        free(ctx->azRcpt);
        ctx->azRcpt = NULL;
        ctx->asRcpt = 0;
    }
    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)
{
    lmtp_res_t res;
    lmtp_rc_t rc = LMTP_EOF;
    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