/*
NAME
mockmta - mock MTA server for use in test suites
SYNOPSIS
mockmta [-d] [-c CERT] [-f CA] [-k KEY] [-p PORT] [-t SEC] MAILBOX
DESCRIPTION
Starts a mock MTA, which behaves almost identically to the real one,
except that it delivers all messages to the given MAILBOX file.
No attempts are made to interpret the data supplied during the STMP
transaction, such as domain names, email addresses, etc, neither is
the material supplied in the DATA command verified to be a valid
email message. Except for being written to MAILBOX, these data are
ignored.
Mockmta can work both as a foreground process and as a standalone
daemon. The foreground mode can be used with GNU pies as follows:
component sm {
mode inetd;
socket inet://0.0.0.0:25;
command "/usr/bin/mockmta /var/spool/mail/dropmail";
}
When run as a daemon, mockmta starts listening on localhost port
PORT (default 25). Incoming SMTP sessions are processed sequentially.
Listening for incoming requests is blocked while an SMTP session is
active.
To support TLS, the program must be compiled with the GnuTLS library.
To do so, make sure the library and its headers are properly installed
and:
cc -omockmta -DWITH_TLS mockmta.c -lgnutls
To enable the STARTTLS ESMTP command, supply the names of the certificate
(-c CERT) and certificate key (-k KEY) files.
OPTIONS
-c CERT Name of the certificate file.
-d Daemon mode
-f CA Name of certificate authority file.
-k KEY Name of the certificate key file.
-p PORT Listen on this port.
-t SEC Reserved for future use.
EXIT CODES
0 Success.
1 Failure (see stderr for details).
2 Command line usage error.
BUGS
At most 32 RCPT commands are allowed.
AUTHOR
Sergey Poznyakoff <gray@gnu.org>
LICENSE
Copyright (C) 2020-2021 Free Software Foundation, Inc.
Mockmta 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 3, or (at your option)
any later version.
Mockmta 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 GNU Mailutils. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <syslog.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
char *progname;
char *mailbox_name;
int daemon_opt;
int idle_timeout;
int port = 25;
int msgid = 1;
enum
{
EX_OK,
EX_FAILURE,
EX_USAGE
};
static void
terror_stderr (char const *fmt, ...)
{
va_list ap;
int m;
static char *fmtbuf = NULL;
static size_t fmtsize = 0;
int ec = errno;
char const *es = NULL;
size_t len;
for (m = 0; fmt[m += strcspn (fmt + m, "%")]; )
{
m++;
if (fmt[m] == 'm')
break;
}
len = strlen (fmt) + 1;
if (fmt[m])
{
es = strerror (ec);
len += strlen (es) - 2;
}
if (len > fmtsize)
{
fmtsize = len;
fmtbuf = realloc (fmtbuf, fmtsize);
if (!fmtbuf)
{
perror ("realloc");
exit (EX_FAILURE);
}
}
if (es)
{
memcpy (fmtbuf, fmt, m - 1);
memcpy (fmtbuf + m - 1, es, strlen (es) + 1);
strcat (fmtbuf, fmt + m + 1);
}
else
strcpy (fmtbuf, fmt);
va_start (ap, fmt);
fprintf (stderr, "%s: ", progname);
vfprintf (stderr, fmtbuf, ap);
fputc ('\n', stderr);
va_end (ap);
}
static void
terror_syslog (char const *fmt, ...)
{
va_list ap;
va_start (ap, fmt);
vsyslog (LOG_ERR, fmt, ap);
va_end (ap);
}
static void (*terror) (char const *fmt, ...) = terror_stderr;
static void
nomemory (void)
{
terror ("out of memory");
exit (EX_FAILURE);
}
static volatile int io_timed_out;
static void
sigalrm (int sig)
{
io_timed_out = 1;
}
struct iodrv
{
int (*drv_read) (void *, char *, size_t, size_t *);
int (*drv_write) (void *, char *, size_t, size_t *);
void (*drv_close) (void *);
const char *(*drv_strerror) (void *, int);
};
#define IOBUFSIZE 1024
struct iobase
{
struct iodrv *iob_drv;
char iob_buf[IOBUFSIZE];
size_t iob_start;
size_t iob_level;
int iob_errno;
int iob_eof;
};
static struct iobase *
iobase_create (struct iodrv *drv, size_t size)
{
struct iobase *bp;
bp = calloc (1, size);
if (!bp)
nomemory ();
bp->iob_drv = drv;
bp->iob_start = 0;
bp->iob_level = 0;
bp->iob_errno = 0;
bp->iob_eof = 0;
return bp;
}
/* Number of data bytes in buffer */
static inline size_t
iobase_data_bytes (struct iobase *bp)
{
return bp->iob_level - bp->iob_start;
}
/* Pointer to the first data byte */
static inline char *
iobase_data_start (struct iobase *bp)
{
return bp->iob_buf + bp->iob_start;
}
static inline void
iobase_data_less (struct iobase *bp, size_t n)
{
bp->iob_start += n;
if (bp->iob_start == bp->iob_level)
{
bp->iob_start = 0;
bp->iob_level = 0;
}
}
static inline int
iobase_data_getc (struct iobase *bp)
{
char *p;
if (iobase_data_bytes (bp) == 0)
return -1;
p = iobase_data_start (bp);
iobase_data_less (bp, 1);
return *p;
}
static inline int
iobase_data_ungetc (struct iobase *bp, int c)
{
if (bp->iob_start > 0)
bp->iob_buf[--bp->iob_start] = c;
else if (bp->iob_level == 0)
bp->iob_buf[bp->iob_level++] = c;
else
return -1;
return 0;
}
/* Number of bytes available for writing in buffer */
static inline size_t
iobase_avail_bytes (struct iobase *bp)
{
return IOBUFSIZE - bp->iob_level;
}
/* Pointer to the first byte available for writing */
static inline char *
iobase_avail_start (struct iobase *bp)
{
return bp->iob_buf + bp->iob_level;
}
static inline void
iobase_avail_less (struct iobase *bp, size_t n)
{
bp->iob_level += n;
}
/* Fill the buffer */
static inline int
iobase_fill (struct iobase *bp)
{
int rc;
size_t n;
rc = bp->iob_drv->drv_read (bp, iobase_avail_start (bp),
iobase_avail_bytes (bp), &n);
if (rc == 0)
{
if (n == 0)
bp->iob_eof = 1;
else
iobase_avail_less (bp, n);
}
bp->iob_errno = rc;
return rc;
}
/* Flush the data available in buffer to external storage */
static inline int
iobase_flush (struct iobase *bp)
{
int rc;
size_t n;
rc = bp->iob_drv->drv_write (bp, iobase_data_start (bp),
iobase_data_bytes (bp), &n);
if (rc == 0)
iobase_data_less (bp, n);
bp->iob_errno = rc;
return rc;
}
static inline char con
|