#include <sys_defs.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <syslog.h>
#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#endif
#include <msg.h>
#include <vstring.h>
#include <vstream.h>
#include <vstring_vstream.h>
#include <get_hostname.h>
#include <listen.h>
#include <events.h>
#include <mymalloc.h>
#include <iostuff.h>
#include <msg_vstream.h>
#include <stringops.h>
#include <smtp_stream.h>
typedef struct SINK_STATE {
VSTREAM *stream;
VSTRING *buffer;
int data_state;
int (*read) (struct SINK_STATE *);
int rcpts;
} SINK_STATE;
#define ST_ANY 0
#define ST_CR 1
#define ST_CR_LF 2
#define ST_CR_LF_DOT 3
#define ST_CR_LF_DOT_CR 4
#define ST_CR_LF_DOT_CR_LF 5
static int var_tmout;
static int var_max_line_length = 2048;
static char *var_myhostname;
static int command_read(SINK_STATE *);
static int data_read(SINK_STATE *);
static void disconnect(SINK_STATE *);
static int count;
static int counter;
static int max_count;
static int disable_pipelining;
static int disable_8bitmime;
static int fixed_delay;
static int disable_esmtp;
static int enable_lmtp;
static int pretend_pix;
static void ehlo_response(SINK_STATE *state)
{
smtp_printf(state->stream, "250-%s", var_myhostname);
if (!disable_pipelining)
smtp_printf(state->stream, "250-PIPELINING");
if (!disable_8bitmime)
smtp_printf(state->stream, "250-8BITMIME");
smtp_printf(state->stream, "250 ");
}
static void helo_response(SINK_STATE *state)
{
smtp_printf(state->stream, "250 %s", var_myhostname);
}
static void ok_response(SINK_STATE *state)
{
smtp_printf(state->stream, "250 Ok");
}
static void mail_response(SINK_STATE *state)
{
state->rcpts = 0;
ok_response(state);
}
static void rcpt_response(SINK_STATE *state)
{
state->rcpts++;
ok_response(state);
}
static void data_response(SINK_STATE *state)
{
state->data_state = ST_CR_LF;
smtp_printf(state->stream, "354 End data with <CR><LF>.<CR><LF>");
state->read = data_read;
}
static void data_event(int unused_event, char *context)
{
SINK_STATE *state = (SINK_STATE *) context;
data_response(state);
}
static void dot_response(SINK_STATE *state)
{
if (enable_lmtp) {
while (state->rcpts-- > 0)
ok_response(state);
} else {
ok_response(state);
}
}
static void quit_response(SINK_STATE *state)
{
smtp_printf(state->stream, "221 Bye");
if (count) {
counter++;
vstream_printf("%d\r", counter);
vstream_fflush(VSTREAM_OUT);
}
}
static int data_read(SINK_STATE *state)
{
int ch;
struct data_trans {
int state;
int want;
int next_state;
};
static struct data_trans data_trans[] = {
ST_ANY, '\r', ST_CR,
ST_CR, '\n', ST_CR_LF,
ST_CR_LF, '.', ST_CR_LF_DOT,
ST_CR_LF_DOT, '\r', ST_CR_LF_DOT_CR,
ST_CR_LF_DOT_CR, '\n', ST_CR_LF_DOT_CR_LF,
};
struct data_trans *dp;
for (;;) {
if ((ch = VSTREAM_GETC(state->stream)) == VSTREAM_EOF)
return (-1);
for (dp = data_trans; dp->state != state->data_state; dp++)
;
if (ch == dp->want)
state->data_state = dp->next_state;
else if (ch == data_trans[0].want)
state->data_state = data_trans[0].next_state;
else
state->data_state = ST_ANY;
if (state->data_state == ST_CR_LF_DOT_CR_LF) {
if (msg_verbose)
msg_info(".");
dot_response(state);
state->read = command_read;
state->data_state = ST_ANY;
break;
}
if (vstream_peek(state->stream) <= 0
&& readable(vstream_fileno(state->stream)) <= 0)
return (0);
}
return (0);
}
typedef struct SINK_COMMAND {
char *name;
void (*response) (SINK_STATE *);
int flags;
} SINK_COMMAND;
#define FLAG_ENABLE (1<<0)
#define FLAG_SYSLOG (1<<1)
static SINK_COMMAND command_table[] = {
"helo", helo_response, 0,
"ehlo", ehlo_response, 0,
"lhlo", ehlo_response, 0,
"mail", mail_response, FLAG_ENABLE,
"rcpt", rcpt_response, FLAG_ENABLE,
"data", data_response, FLAG_ENABLE,
"rset", ok_response, FLAG_ENABLE,
"noop", ok_response, FLAG_ENABLE,
"vrfy", ok_response, FLAG_ENABLE,
"quit", quit_response, FLAG_ENABLE,
0,
};
static void set_cmd_flags(const char *cmd, int flags)
{
SINK_COMMAND *cmdp;
for (cmdp = command_table; cmdp->name != 0; cmdp++)
if (strcasecmp(cmd, cmdp->name) == 0)
break;
if (cmdp->name == 0)
msg_fatal("unknown command: %s", cmd);
cmdp->flags |= flags;
}
static void set_cmds_flags(const char *cmds, int flags)
{
char *saved_cmds;
char *cp;
char *cmd;
saved_cmds = cp = mystrdup(cmds);
while ((cmd = mystrtok(&cp, " \t\r\n,")) != 0)
set_cmd_flags(cmd, flags);
myfree(saved_cmds);
}
static int command_read(SINK_STATE *state)
{
char *command;
SINK_COMMAND *cmdp;
int ch;
struct cmd_trans {
int state;
int want;
int next_state;
};
static struct cmd_trans cmd_trans[] = {
ST_ANY, '\r', ST_CR,
ST_CR, '\n', ST_CR_LF,
};
struct cmd_trans *cp;
char *ptr;
for (;;) {
if ((ch = VSTREAM_GETC(state->stream)) == VSTREAM_EOF)
return (-1);
if (VSTRING_LEN(state->buffer) >= var_max_line_length) {
msg_warn("command line too long");
return (-1);
}
VSTRING_ADDCH(state->buffer, ch);
for (cp = cmd_trans; cp->state != state->data_state; cp++)
;
if (ch == cp->want)
state->data_state = cp->next_state;
else if (ch == cmd_trans[0].want)
state->data_state = cmd_trans[0].next_state;
else
state->data_state = ST_ANY;
if (state->data_state == ST_CR_LF)
break;
if (vstream_peek(state->stream) <= 0
&& readable(vstream_fileno(state->stream)) <= 0)
return (0);
}
vstring_truncate(state->buffer, VSTRING_LEN(state->buffer) - 2);
VSTRING_TERMINATE(state->buffer);
state->data_state = ST_ANY;
VSTRING_RESET(state->buffer);
ptr = vstring_str(state->buffer);
if ((command = mystrtok(&ptr, " \t")) == 0) {
smtp_printf(state->stream, "500 Error: unknown command");
return (0);
}
if (msg_verbose)
msg_info("%s", command);
for (cmdp = command_table; cmdp->name != 0; cmdp++)
if (strcasecmp(command, cmdp->name) == 0)
break;
if (cmdp->name == 0 || (cmdp->flags & FLAG_ENABLE) == 0) {
smtp_printf(state->stream, "500 Error: unknown command");
return (0);
}
if (cmdp->flags & FLAG_SYSLOG)
syslog(LOG_INFO, "%s %.100s", command, printable(ptr, '?'));
if (cmdp->response == data_response && fixed_delay > 0) {
event_request_timer(data_event, (char *) state, fixed_delay);
} else {
cmdp->response(state);
if (cmdp->response == quit_response)
return (-1);
}
return (0);
}
static void read_event(int unused_event, char *context)
{
SINK_STATE *state = (SINK_STATE *) context;
do {
switch (vstream_setjmp(state->stream)) {
default:
msg_panic("unknown error reading input");
case SMTP_ERR_TIME:
msg_panic("attempt to read non-readable socket");
case SMTP_ERR_EOF:
msg_warn("lost connection");
disconnect(state);
return;
case 0:
if (state->read(state) < 0) {
if (msg_verbose)
msg_info("disconnect");
disconnect(state);
return;
}
}
} while (vstream_peek(state->stream) > 0);
}
static void disconnect(SINK_STATE *state)
{
event_disable_readwrite(vstream_fileno(state->stream));
vstream_fclose(state->stream);
vstring_free(state->buffer);
myfree((char *) state);
if (max_count > 0 && ++counter >= max_count)
exit(0);
}
static void connect_event(int unused_event, char *context)
{
int sock = CAST_CHAR_PTR_TO_INT(context);
struct sockaddr sa;
SOCKADDR_SIZE len = sizeof(sa);
SINK_STATE *state;
int fd;
if ((fd = accept(sock, &sa, &len)) >= 0) {
if (msg_verbose)
msg_info("connect (%s)",
#ifdef AF_LOCAL
sa.sa_family == AF_LOCAL ? "AF_LOCAL" :
#else
sa.sa_family == AF_UNIX ? "AF_UNIX" :
#endif
sa.sa_family == AF_INET ? "AF_INET" :
#ifdef AF_INET6
sa.sa_family == AF_INET6 ? "AF_INET6" :
#endif
"unknown protocol family");
non_blocking(fd, NON_BLOCKING);
state = (SINK_STATE *) mymalloc(sizeof(*state));
state->stream = vstream_fdopen(fd, O_RDWR);
state->buffer = vstring_alloc(1024);
state->read = command_read;
state->data_state = ST_ANY;
smtp_timeout_setup(state->stream, var_tmout);
if (pretend_pix)
smtp_printf(state->stream, "220 ********");
else if (disable_esmtp)
smtp_printf(state->stream, "220 %s", var_myhostname);
else
smtp_printf(state->stream, "220 %s ESMTP", var_myhostname);
event_enable_read(fd, read_event, (char *) state);
}
}
static void usage(char *myname)
{
msg_fatal("usage: %s [-ceLpPv8] [-h hostname] [-n count] [-s commands] [-w delay] [host]:port backlog", myname);
}
int main(int argc, char **argv)
{
int sock;
int backlog;
int ch;
msg_vstream_init(argv[0], VSTREAM_ERR);
while ((ch = GETOPT(argc, argv, "ceh:Ln:pPs:vw:8")) > 0) {
switch (ch) {
case 'c':
count++;
break;
case 'e':
disable_esmtp = 1;
break;
case 'h':
var_myhostname = optarg;
break;
case 'L':
enable_lmtp = 1;
break;
case 'n':
max_count = atoi(optarg);
break;
case 'p':
disable_pipelining = 1;
break;
case 'P':
pretend_pix = 1;
break;
case 's':
openlog(basename(argv[0]), LOG_PID, LOG_MAIL);
set_cmds_flags(optarg, FLAG_SYSLOG);
break;
case 'v':
msg_verbose++;
break;
case 'w':
if ((fixed_delay = atoi(optarg)) <= 0)
usage(argv[0]);
break;
case '8':
disable_8bitmime = 1;
break;
default:
usage(argv[0]);
}
}
if (argc - optind != 2)
usage(argv[0]);
if ((backlog = atoi(argv[optind + 1])) <= 0)
usage(argv[0]);
if (var_myhostname == 0)
var_myhostname = "smtp-sink";
set_cmds_flags(enable_lmtp ? "lhlo" :
disable_esmtp ? "helo" :
"helo, ehlo", FLAG_ENABLE);
if (strncmp(argv[optind], "unix:", 5) == 0) {
sock = unix_listen(argv[optind] + 5, backlog, BLOCKING);
} else {
if (strncmp(argv[optind], "inet:", 5) == 0)
argv[optind] += 5;
sock = inet_listen(argv[optind], backlog, BLOCKING);
}
event_enable_read(sock, connect_event, CAST_INT_TO_CHAR_PTR(sock));
for (;;)
event_loop(-1);
}