/*
** Copyright (c) 2001 The OSSP Project
** Copyright (c) 2001 Cable & Wireless Deutschland
**
** This file is part of OSSP lmtp2nntp, an LMTP speaking local
** mailer which forwards mails as Usenet news articles via NNTP.
** It can be found at http://www.ossp.org/pkg/lmtp2nntp/.
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public License
** as published by the Free Software Foundation; either version
** 2.0 of the License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this file; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
** USA, or contact the OSSP project .
**
** lmtp2nntp.c: LMTP to NNTP main procedure
*/
#include
#include
#include
#include
#include
#include
#include
#include
/* third party */
#include "str.h"
#include "argz.h"
#include "shpat_match.h"
#include "l2.h"
/* own headers */
#include "lmtp2nntp.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#if defined(HAVE_DMALLOC_H) && defined(DMALLOC)
#include "dmalloc.h"
#endif
#include "lmtp.h"
#include "nntp.h"
#include "sa.h"
#include "msg.h"
#define _VERSION_C_AS_HEADER_
#include "version.c"
#undef _VERSION_C_AS_HEADER_
#ifndef FALSE
#define FALSE (1 != 1)
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif
#ifndef NUL
#define NUL '\0'
#endif
#define ERR_EXECUTION -1
#define ERR_DELIVERY -2
#define STDSTRLEN 128
#define MAXNEWSSERVICES 16
extern void lmtp_debug_dumplmtp(lmtp_t *lmtp);
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 int helo_rfc0821domain(char *msg, char **domain);
static int helo_rfc1035domain(char *msg, char **domain);
struct session {
int lhlo_seen;
char *lhlo_domain;
};
static int connect_nonb(int, const struct sockaddr *, socklen_t, int);
static void initsession(struct session *session);
static void resetsession(struct session *session);
int groupmatch(char *, size_t, char *);
struct ns {
char *h; /* host */
char *p; /* port */
sa_t *sa;
int s; /* socket */
nntp_t *nntp;
nntp_rc_t rc;
};
typedef struct {
char *progname;
char *option_logfile;
int option_groupmode;
int option_deliverymode;
char *option_deliverymodefakestatus;
char *option_deliverymodefakedsn;
int option_maxmessagesize;
int option_waittime;
char *option_mailfrom;
unsigned int option_levelmask;
l2_stream_t *l2;
char *cpBindh;
char *cpBindp;
sa_t *saBind;
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,
GROUPMODE_HEADER
};
enum {
DELIVERYMODE_FAKE,
DELIVERYMODE_POST,
DELIVERYMODE_FEED
};
/*
* print usage information
*/
static void usage(char *command)
{
/* use
* perl ) { if(m/\/\*POD (.*) .*\*\//) { $_=$1; s/.<(.*?)>/$1/g ; print "\"$_\"\n" };}'
* to pull the USAGE string out of this source
*/
fprintf(stderr,
"USAGE: %s "
"[-b bindaddr[:port]"
"[-d deliverymode]"
"[-g groupmode]"
"[-h host[:port][,host[:port], ...]]"
"[-m mailfrom]"
"[-n nodename]"
"[-s size]"
"[-l level[:logfile]]"
"[-v]"
"[-w waittime]"
"newsgroup [newsgroup ...]"
"\n",
command);
return;
}
static ssize_t trace_lmtp_read(void *_ctx, int d, void *buf, size_t nbytes)
{
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
ssize_t rc;
rc = read(d, buf, nbytes);
if (rc == -1)
log0(ctx, TRACE, "LMTP read error: %m");
else
log3(ctx, TRACE, "LMTP %5d << \"%{text}D\"", rc, buf, rc);
return rc;
}
static ssize_t trace_lmtp_write(void *_ctx, int d, const void *buf, size_t nbytes)
{
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
ssize_t rc;
log3(ctx, TRACE, "LMTP %5d >> \"%{text}D\"", nbytes, buf, nbytes);
rc = write(d, buf, nbytes);
if (rc == -1)
log0(ctx, TRACE, "LMTP write error: %m");
return rc;
}
static ssize_t trace_nntp_read(void *_ctx, int d, void *buf, size_t nbytes)
{
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
ssize_t rc;
rc = read(d, buf, nbytes);
if (rc == -1)
log0(ctx, TRACE, "NNTP read error: %m");
else
log3(ctx, TRACE, "NNTP %5d << \"%{text}D\"", rc, buf, rc);
return rc;
}
static ssize_t trace_nntp_write(void *_ctx, int d, const void *buf, size_t nbytes)
{
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx;
ssize_t rc;
log3(ctx, TRACE, "NNTP %5d >> \"%{text}D\"", nbytes, buf, nbytes);
rc = write(d, buf, nbytes);
if (rc == -1)
log0(ctx, TRACE, "NNTP write error: %m");
return rc;
}
static l2_result_t
formatter_prefix(l2_context_t *_ctx, const char id, const char *param,
char *bufptr, size_t bufsize, size_t *buflen, va_list *ap)
{
lmtp2nntp_t *ctx = (lmtp2nntp_t *)_ctx->vp;
sprintf(bufptr, "%s[%ld]", ctx->progname, (long)getpid());
*buflen = strlen(bufptr);
return L2_OK;
}
static l2_result_t
formatter_errno(l2_context_t *_ctx, const char id, const char *param,
char *bufptr, size_t bufsize, size_t *buflen, va_list *ap)
{
sprintf(bufptr, "(%d) %s", errno, strerror(errno));
*buflen = strlen(bufptr);
return L2_OK;
}
int main(int argc, char **argv)
{
lmtp_t *lmtp;
lmtp_io_t lmtp_io;
lmtp2nntp_t *ctx;
int i; /* general purpose scratch int, index ... */
char *cp; /* general purpose character pointer */
char *azHosts;
size_t asHosts;
char *cpHost;
char *cpPort;
sa_t *sa;
l2_channel_t *chPrefix;
l2_channel_t *chBuf;
l2_channel_t *chFile;
/* create application context */
if ((ctx = (lmtp2nntp_t *)malloc(sizeof(lmtp2nntp_t))) == NULL)
exit(ERR_EXECUTION);
ctx->progname = strdup(argv[0]);
ctx->option_logfile = NULL;
ctx->option_groupmode = GROUPMODE_ARG;
ctx->option_deliverymode = DELIVERYMODE_FAKE;
ctx->option_deliverymodefakestatus = "553"; /* Requested action not taken: mailbox name not allowed */
ctx->option_deliverymodefakedsn = "5.7.1"; /* Delivery not authorized, message refused */
ctx->option_maxmessagesize = 8 * 1024 * 1024;
ctx->option_waittime = -1;
ctx->option_mailfrom = NULL;
ctx->option_levelmask = L2_LEVEL_UPTO(L2_LEVEL_WARNING);
ctx->l2 = NULL;
ctx->cpBindh = NULL;
ctx->cpBindp = NULL;
ctx->saBind = NULL;
ctx->nsc = 0;
for (i=0; i < MAXNEWSSERVICES; i++) {
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) == -1) {
fprintf(stderr, "%s:Error: uname failed \"%s\"\n", ctx->progname, strerror(errno));
exit(ERR_EXECUTION);
}
#if 0
{
char buf[1000];
int bufused = 0;
int tracefile;
for (i=0; i */
/* use
* perl ) { if(m/\/\*POD (.*) .*\*\//) { $_=$1; print "$_\n" };}'
* to pull the POD SYNOPSIS header directly out of this source
*/
/* read in the arguments */
while ((i = getopt(argc, argv, "b:d:g:h:l:m:n:s:vw:")) != -1) {
switch (i) {
case 'b': /*POD [B<-b> I[I<:port>] */
/* parse host[:port] string into host and port */
ctx->cpBindh = strdup(optarg);
if ((ctx->cpBindp = strrchr(ctx->cpBindh, ':')) != NULL) {
*ctx->cpBindp++ = NUL;
ctx->cpBindp = strdup(ctx->cpBindp);
}
else
ctx->cpBindp = strdup("0");
if ((ctx->saBind = sa_create(SA_IP, "tcp", ctx->cpBindh, ctx->cpBindp)) == NULL) {
fprintf(stderr, "%s:Error: creating TCP socket address failed for \"%s:%s\": %s\n",
ctx->progname,
ctx->cpBindh,
ctx->cpBindp,
strerror(errno));
exit(ERR_EXECUTION);
}
break;
case 'd': /*POD [B<-d> I] */
if (strcasecmp(optarg, "post") == 0)
ctx->option_deliverymode = DELIVERYMODE_POST;
else if (strcasecmp(optarg, "feed") == 0)
ctx->option_deliverymode = DELIVERYMODE_FEED;
else {
if (strlen(optarg) != 9) {
fprintf(stderr, "%s:Error: Invalid format or length \"%s\" to option -d\n", ctx->progname, optarg);
exit(ERR_EXECUTION);
}
if (optarg[3] != '/') {
fprintf(stderr, "%s:Error: Invalid format or missing slash \"%s\" to option -d\n", ctx->progname, optarg);
exit(ERR_EXECUTION);
}
optarg[3] = NUL;
ctx->option_deliverymodefakestatus = &optarg[0];
ctx->option_deliverymodefakedsn = &optarg[4];
if ( strlen(ctx->option_deliverymodefakestatus) != 3
|| !isdigit((int)ctx->option_deliverymodefakestatus[0])
|| !isdigit((int)ctx->option_deliverymodefakestatus[1])
|| !isdigit((int)ctx->option_deliverymodefakestatus[2])) {
fprintf(stderr, "%s:Error: Invalid status in format \"%s\" to option -d\n", ctx->progname, optarg);
exit(ERR_EXECUTION);
}
if ( (strlen(ctx->option_deliverymodefakedsn) != 5)
|| !isdigit((int)ctx->option_deliverymodefakedsn[0])
|| (ctx->option_deliverymodefakedsn[1] != '.')
|| !isdigit((int)ctx->option_deliverymodefakedsn[2])
|| (ctx->option_deliverymodefakedsn[3] != '.')
|| !isdigit((int)ctx->option_deliverymodefakedsn[4])
|| (ctx->option_deliverymodefakedsn[0] != ctx->option_deliverymodefakestatus[0])) {
fprintf(stderr, "%s:Error: Invalid dsn in format \"%s\" to option -d\n", ctx->progname, optarg);
exit(ERR_EXECUTION);
}
}
break;
case 'g': /*POD [B<-g> I] */
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", ctx->progname, optarg);
exit(ERR_EXECUTION);
}
break;
case 'h': /*POD [B<-h> I[I<:port>][,I[I<:port>], ...]] */
if (argz_create_sep(optarg, ',', &azHosts, &asHosts) != 0)
exit(ERR_EXECUTION);
cp = NULL;
while ((cp = argz_next(azHosts, asHosts, cp)) != NULL) {
if (ctx->nsc >= MAXNEWSSERVICES) {
fprintf(stderr, "%s:Error: Too many services (%d) using option -h\n", ctx->progname, ctx->nsc);
exit(ERR_EXECUTION);
}
/* parse host[:port] string into host and port */
cpHost = strdup(cp);
if ((cpPort = strrchr(cpHost, ':')) != NULL) {
*cpPort++ = NUL;
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",
ctx->progname,
ctx->ns[ctx->nsc].h,
ctx->ns[ctx->nsc].p,
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",
ctx->progname,
ctx->ns[ctx->nsc].h,
ctx->ns[ctx->nsc].p,
strerror(errno));
exit(ERR_EXECUTION);
}
ctx->ns[ctx->nsc].nntp = NULL;
ctx->nsc++;
}
free(azHosts);
break;
case 'm': /*POD [B<-m> I] */
ctx->option_mailfrom = strdup(optarg);
if (str_parse("<>", ctx->option_mailfrom) == -1) {
fprintf(stderr, "%s:Error: illegal regex \"%s\" to option -m.\n", ctx->progname, ctx->option_mailfrom);
exit(ERR_EXECUTION);
}
break;
case 'n': /*POD [B<-n> I] */
if (strlen(optarg) > sizeof(ctx->uname.nodename)-1) {
fprintf(stderr, "%s:Error: nodename \"%s\" to long to option -n.\n", ctx->progname, optarg);
exit(ERR_EXECUTION);
}
strcpy(ctx->uname.nodename, optarg);
break;
case 's': /*POD [B<-s> I] */
ctx->option_maxmessagesize = atoi(optarg);
if(ctx->option_maxmessagesize < 64) {
fprintf(stderr, "%s:Error: maximum message size is unacceptable small.\n", ctx->progname);
exit(ERR_EXECUTION);
}
case 'l': /*POD [B<-l> I[:I]] */
if ((cp = strrchr(optarg, ':')) != NULL) {
*cp++ = NUL;
if (*cp == NUL) {
fprintf(stderr, "%s:Error: empty logfile to option -l\n", ctx->progname);
exit(ERR_EXECUTION);
}
else
ctx->option_logfile = strdup(cp);
}
else
ctx->option_logfile = strdup("logfile");
if (l2_util_s2l(optarg, strlen(optarg), ',', &ctx->option_levelmask) != L2_OK) {
fprintf(stderr, "%s:Error: invalid level \"%s\" to option -l\n", ctx->progname, optarg);
exit(ERR_EXECUTION);
}
ctx->option_levelmask = L2_LEVEL_UPTO(ctx->option_levelmask);
break;
case 'v': /*POD [B<-v>] (version)*/
fprintf(stdout, "%s\n", lmtp2nntp_version.v_gnu);
exit(0);
break;
case 'w': /*POD [B<-w> I] */
ctx->option_waittime = atoi(optarg);
if(ctx->option_waittime < 1) {
fprintf(stderr, "%s:Error: waittime %d to option -w must be greater 1 second.\n", ctx->progname, ctx->option_waittime);
exit(ERR_EXECUTION);
}
break;
case '?':
default:
usage(ctx->progname);
exit(ERR_EXECUTION);
}
}
/*POD I [I ...] */
for (i = optind; i < argc; i++) {
argz_add(&ctx->azGroupargs, &ctx->asGroupargs, argv[i]);
}
if ((ctx->l2 = l2_stream_create()) == NULL) {
fprintf(stderr, "%s:Error: logging failed to create stream\n", ctx->progname);
exit(ERR_EXECUTION);
}
if (l2_stream_formatter(ctx->l2, 'P', formatter_prefix, (l2_context_t *)&ctx) != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to register formatter\n", ctx->progname);
exit(ERR_EXECUTION);
}
if (l2_stream_formatter(ctx->l2, 'D', l2_util_fmt_dump, NULL) != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to register dump formatter\n", ctx->progname);
exit(ERR_EXECUTION);
}
if (l2_stream_formatter(ctx->l2, 'S', l2_util_fmt_string, NULL) != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to register string formatter\n", ctx->progname);
exit(ERR_EXECUTION);
}
if (l2_stream_formatter(ctx->l2, 'm', formatter_errno, NULL) != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to register dump formatter\n", ctx->progname);
exit(ERR_EXECUTION);
}
if ((chPrefix = l2_channel_create(&l2_handler_prefix)) == NULL) {
fprintf(stderr, "%s:Error: logging failed to create prefix channel\n", ctx->progname);
exit(ERR_EXECUTION);
}
if (l2_channel_configure(chPrefix, "timefmt,timezone", "[%d-%m-%Y/%H:%M:%S] ", "local") != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to configure prefix channel\n", ctx->progname);
exit(ERR_EXECUTION);
}
if ((chBuf = l2_channel_create(&l2_handler_buffer)) == NULL) {
fprintf(stderr, "%s:Error: logging failed to create buffer channel\n", ctx->progname);
exit(ERR_EXECUTION);
}
if (l2_channel_configure(chBuf, "size", 4) != L2_OK) { //FIXME 4096
fprintf(stderr, "%s:Error: logging failed to configure buffer channel\n", ctx->progname);
exit(ERR_EXECUTION);
}
if ((chFile = l2_channel_create(&l2_handler_file)) == NULL) {
fprintf(stderr, "%s:Error: logging failed to create file channel\n", ctx->progname);
exit(ERR_EXECUTION);
}
if (l2_channel_configure(chFile, "path,append,perm", ctx->option_logfile, TRUE, 0644) != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to configure file channel\n", ctx->progname);
exit(ERR_EXECUTION);
}
if (l2_channel_stack(chFile, chBuf) != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to stack buffer channel on top of file channel\n", ctx->progname);
exit(ERR_EXECUTION);
}
if (l2_channel_stack(chBuf, chPrefix) != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to stack prefix channel on top of buffer channel\n", ctx->progname);
exit(ERR_EXECUTION);
}
if (l2_channel_open(chPrefix) != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to open buffer channel\n", ctx->progname);
exit(ERR_EXECUTION);
}
if (l2_stream_channel(ctx->l2, chPrefix, L2_LEVEL_UPTO(L2_LEVEL_DEBUG)) != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to attach channel into stream\n", ctx->progname);
exit(ERR_EXECUTION);
}
if (l2_stream_levels(ctx->l2, ctx->option_levelmask) != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to set global logging level\n", ctx->progname);
exit(ERR_EXECUTION);
}
if (l2_stream_log(ctx->l2, L2_LEVEL_INFO, "%P: startup, version %s", lmtp2nntp_version.v_gnu) != L2_OK) {
fprintf(stderr, "%s:Error: logging failed to log startup message to stream\n", ctx->progname);
exit(ERR_EXECUTION);
}
/* initialize LMTP context */
lmtp_io.ctx = ctx;
lmtp_io.select = NULL;
lmtp_io.read = trace_lmtp_read;
lmtp_io.write = trace_lmtp_write;
if ((lmtp = lmtp_create(STDIN_FILENO, STDOUT_FILENO,
(ctx->option_logfile && (ctx->option_levelmask >= L2_LEVEL_TRACE)) ?
&lmtp_io : NULL )) == NULL) {
fprintf(stderr, "%s:Error: Unable to initialize LMTP library\n", ctx->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
* MAIL FROM:
* RCPT TO:
* DATA
* RSET
* NOOP
* QUIT
*/
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, "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 */
log0(ctx, INFO, "%P: graceful shutdown, no more logging until exit");
lmtp_gfs_quit(ctx);
lmtp_gfs_lhlo(ctx);
lmtp_destroy(lmtp);
if (ctx->option_logfile != NULL)
free(ctx->option_logfile);
if (ctx->progname != NULL)
free(ctx->progname);
if (ctx->azGroupargs != NULL)
free(ctx->azGroupargs);
if (ctx != NULL)
free(ctx);
str_parse(NULL, NULL);
l2_stream_destroy(ctx->l2);
return 0;
}
/* taken from "UNIX Network Programming", Volume 1, second edition W. Richard
* Stevens, connect_nonb.c from section 15.4 "Nonblocking connect", page 411,
* http://www.kohala.com/start/
*/
int connect_nonb(int sockfd, const struct sockaddr *saptr, socklen_t salen, int nsec)
{
int flags, n, error;
socklen_t len;
fd_set rset, wset;
struct timeval tval;
flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
error = 0;
if ( (n = connect(sockfd, (struct sockaddr *) saptr, salen)) < 0)
if (errno != EINPROGRESS)
return(-1);
/* Do whatever we want while the connect is taking place. */
if (n == 0)
goto done; /* connect completed immediately */
FD_ZERO(&rset);
FD_SET(sockfd, &rset);
wset = rset;
tval.tv_sec = nsec;
tval.tv_usec = 0;
if ( (n = select(sockfd+1, &rset, &wset, NULL,
nsec ? &tval : NULL)) == 0) {
close(sockfd); /* timeout */
errno = ETIMEDOUT;
return(-1);
}
if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
len = sizeof(error);
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
return(-1); /* Solaris pending error */
} else
return(-1); /* err_quit("select error: sockfd not set"); */
done:
fcntl(sockfd, F_SETFL, flags); /* restore file status flags */
if (error) {
close(sockfd); /* just in case */
errno = error;
return(-1);
}
return(0);
}
static void resetsession(struct session *session)
{
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 lmtp_rc_t lmtp_cb_lhlo(lmtp_t *lmtp, lmtp_io_t *io, lmtp_req_t *req, void *_ctx)
{
/*
* 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
* 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_res_t res;
nntp_rc_t rc;
char str[STDSTRLEN];
int bOk;
int i;
nntp_io_t nntp_io;
log0(ctx, INFO, "lmtp_cb_lhlo");
nntp_io.ctx = ctx;
nntp_io.select = NULL;
nntp_io.read = trace_nntp_read;
nntp_io.write = trace_nntp_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) {
res.statuscode = "503";
res.dsncode = "5.0.0";
res.statusmsg = "Duplicate LHLO.";
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.0.0 Other undefined Status
*/
if (! ( helo_rfc0821domain(req->msg, &ctx->session.lhlo_domain) > 0
|| helo_rfc1035domain(req->msg, &ctx->session.lhlo_domain) > 0)) {
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;
}
/* 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 = "451";
res.dsncode = "4.3.5";
res.statusmsg = "No valid NNTP services configured.";
lmtp_response(lmtp, &res);
return LMTP_OK;
}
i = 0;
do {
bOk = TRUE;
if (bOk && (ctx->saBind != NULL)) {
log0(ctx, DEBUG, "bind");
if (bind(ctx->ns[i].s, ctx->saBind->sa_buf, ctx->saBind->sa_len) < 0) {
bOk = FALSE;
log0(ctx, ERROR, "bind=%m");
}
}
if (bOk) {
if(ctx->option_waittime > 0) {
log0(ctx, DEBUG, "connect_nonb");
if (connect_nonb(ctx->ns[i].s, ctx->ns[i].sa->sa_buf, ctx->ns[i].sa->sa_len,
ctx->option_waittime) < 0) {
bOk = FALSE;
log0(ctx, ERROR, "connect_nonb=%m");
}
}
else {
log0(ctx, DEBUG, "connect");
if (connect(ctx->ns[i].s, ctx->ns[i].sa->sa_buf, ctx->ns[i].sa->sa_len) < 0) {
bOk = FALSE;
log0(ctx, ERROR, "connect=%m");
}
}
}
if (bOk) {
log0(ctx, DEBUG, "nntp_create");
if ((ctx->ns[i].nntp = nntp_create(ctx->ns[i].s, ctx->ns[i].s,
(ctx->option_logfile && (ctx->option_levelmask >= L2_LEVEL_TRACE)) ?
&nntp_io : NULL)) == NULL) {
bOk = FALSE;
log0(ctx, ERROR, "nntp_create failed");
}
}
if (bOk && ctx->option_waittime >= 0) {
log1(ctx, DEBUG, "nntp_timeout(%d)", ctx->option_waittime);
nntp_timeout(ctx->ns[i].nntp, ctx->option_waittime);
}
if (bOk) {
log0(ctx, DEBUG, "nntp_init");
if ((rc = nntp_init(ctx->ns[i].nntp)) != NNTP_OK) {
bOk = FALSE;
log2(ctx, ERROR, "nntp_init=(%m) %s", rc, nntp_error(rc));
}
}
if (bOk) {
log0(ctx, INFO, "nntp connection successfully established");
i++;
}
else {
log0(ctx, INFO, "nntp connection establishment failed");
if (i < --ctx->nsc) {
memcpy(&ctx->ns[i], &ctx->ns[i+1], (ctx->nsc - i ) * sizeof(struct ns));
}
}
} while (i < ctx->nsc);
/* RFC0821 4.2.1. REPLY CODES BY FUNCTION GROUPS 421 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 = "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),
"%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.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[i].sa);
if (ctx->ns[i].p != NULL)
free(ctx->ns[i].p);
if (ctx->ns[i].h != NULL)
free(ctx->ns[i].h);
}
if (ctx->option_mailfrom != NULL)
free(ctx->option_mailfrom);
if (ctx->cpBindh != NULL)
free(ctx->cpBindh);
if (ctx->cpBindp != NULL)
free(ctx->cpBindp);
}
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 according to RFC0821:
# ::= 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 .= "\n";
$cregex =~ s|\\|\\\\|sg;
$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"
"-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 according to RFC1035:
# ::= 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
# ::= |
# ::= | "-"
# ::= |
#