/* * 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, * */ #include #include #include #include #include #include /* 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; ioption_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 */ 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 according to RFC 821: # ::= one, two, or three digits representing a decimal integer value in the range 0 through 255 # ::= any one of the 52 alphabetic characters A through Z in upper case and a through z in lower case # ::= any one of the ten digits 0 through 9 # ::= | | "-" # ::= | # ::= | # ::= "." "." "." # ::= | # ::= # ::= | "#" | "[" "]" # ::= | "." # 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 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; }