diff options
author | Sergey Poznyakoff <gray@gnu.org> | 2021-06-12 22:28:29 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org> | 2021-06-12 22:28:29 +0300 |
commit | a016e5ce3c2ac119e3d38a91cc338715b5e3a479 (patch) | |
tree | 033c74a6490762abccabb9b635a4c49532c1b918 | |
download | mockmta-a016e5ce3c2ac119e3d38a91cc338715b5e3a479.tar.gz mockmta-a016e5ce3c2ac119e3d38a91cc338715b5e3a479.tar.bz2 |
Initial commit
-rw-r--r-- | mockmta.c | 1585 |
1 files changed, 1585 insertions, 0 deletions
diff --git a/mockmta.c b/mockmta.c new file mode 100644 index 0000000..8c276d6 --- /dev/null +++ b/mockmta.c @@ -0,0 +1,1585 @@ +/* + 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 const * +iobase_strerror (struct iobase *bp) +{ + return bp->iob_drv->drv_strerror (bp, bp->iob_errno); +} + +static inline int +iobase_eof (struct iobase *bp) +{ + return bp->iob_eof && iobase_data_bytes (bp) == 0; +} + +#if 0 +/* Not actually used. Provided for completeness sake. */ +static ssize_t +iobase_read (struct iobase *bp, char *buf, size_t size) +{ + size_t len = 0; + + while (size) + { + size_t n = iobase_data_bytes (bp); + if (n == 0) + { + if (bp->iob_eof) + break; + if (iobase_fill (bp)) + break; + continue; + } + if (n > size) + n = size; + memcpy (buf, iobase_data_start (bp), n); + iobase_data_less (bp, n); + len += n; + buf += n; + size -= n; + } + + if (len == 0 && bp->iob_errno) + return -1; + return len; +} +#endif + +static ssize_t +iobase_readln (struct iobase *bp, char *buf, size_t size) +{ + size_t len = 0; + int cr_seen = 0; + + size--; + if (idle_timeout) + alarm (idle_timeout); + while (len < size) + { + int c = iobase_data_getc (bp); + if (c < 0) + { + if (bp->iob_eof) + break; + if (iobase_fill (bp) || io_timed_out) + break; + continue; + } + if (c == '\r') + { + cr_seen = 1; + continue; + } + if (c != '\n' && cr_seen) + { + buf[len++] = '\r'; + if (len == size) + { + if (iobase_data_ungetc (bp, c)) + abort (); + break; + } + } + cr_seen = 0; + buf[len++] = c; + if (c == '\n') + break; + } + if (idle_timeout) + alarm (0); + if (io_timed_out) + { + bp->iob_errno = ETIMEDOUT; + return -1; + } + if (len == 0 && bp->iob_errno) + return -1; + buf[len] = 0; + return len; +} + +static ssize_t +iobase_write (struct iobase *bp, char *buf, size_t size) +{ + size_t len = 0; + + while (size) + { + size_t n = iobase_avail_bytes (bp); + if (n == 0) + { + if (iobase_flush (bp)) + break; + continue; + } + if (n > size) + n = size; + memcpy (iobase_avail_start (bp), buf + len, n); + iobase_avail_less (bp, n); + len += n; + size -= n; + } + if (len == 0 && bp->iob_errno) + return -1; + return len; +} + +static ssize_t +iobase_writeln (struct iobase *bp, char *buf, size_t size) +{ + size_t len = 0; + + while (size) + { + char *p = memchr (buf, '\n', size); + size_t n = p ? p - buf + 1 : size; + ssize_t rc = iobase_write (bp, buf, n); + if (rc <= 0) + break; + if (p && iobase_flush (bp)) + break; + buf = p; + len += rc; + size -= rc; + } + if (len == 0 && bp->iob_errno) + return -1; + return len; +} + +static void +iobase_close (struct iobase *bp) +{ + bp->iob_drv->drv_close (bp); + free (bp); +} + +/* File-descriptor I/O streams */ +struct iofile +{ + struct iobase base; + int fd; +}; + +static int +iofile_read (void *sd, char *data, size_t size, size_t *nbytes) +{ + struct iofile *bp = sd; + ssize_t n = read (bp->fd, data, size); + if (n == -1) + return errno; + if (nbytes) + *nbytes = n; + return 0; +} + +static int +iofile_write (void *sd, char *data, size_t size, size_t *nbytes) +{ + struct iofile *bp = sd; + ssize_t n = write (bp->fd, data, size); + if (n == -1) + return errno; + if (nbytes) + *nbytes = n; + return 0; +} + +static const char * +iofile_strerror (void *sd, int rc) +{ + return strerror (rc); +} + +static void +iofile_close (void *sd) +{ + struct iofile *bp = sd; + close (bp->fd); +} + +static struct iodrv iofile_drv = { + iofile_read, + iofile_write, + iofile_close, + iofile_strerror +}; + +struct iobase * +iofile_create (int fd) +{ + struct iofile *bp = (struct iofile *) iobase_create (&iofile_drv, sizeof (*bp)); + bp->fd = fd; + return (struct iobase*) bp; +} + +enum + { + IO2_RD, + IO2_WR + }; + +static void disable_starttls (void); + +#ifdef WITH_TLS +# include <gnutls/gnutls.h> + +/* TLS support */ +char *tls_cert; /* TLS sertificate */ +char *tls_key; /* TLS key */ +char *tls_cafile; + +static inline int +set_tls_opt (int c) +{ + switch (c) + { + case 'c': + tls_cert = optarg; + break; + + case 'k': + tls_key = optarg; + break; + + case 'f': + tls_cafile = optarg; + break; + + default: + return 1; + } + return 0; +} + +static inline int +enable_tls (void) +{ + return tls_cert != NULL && tls_key != NULL; +} + +/* TLS streams */ +struct iotls +{ + struct iobase base; + gnutls_session_t sess; + int fd[2]; +}; + +static int +iotls_read (void *sd, char *data, size_t size, size_t *nbytes) +{ + struct iotls *iob = sd; + int rc; + + do + rc = gnutls_record_recv (iob->sess, data, size); + while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED); + if (rc >= 0) + { + if (nbytes) + *nbytes = rc; + return 0; + } + return rc; +} + +static int +iotls_write (void *sd, char *data, size_t size, size_t *nbytes) +{ + struct iotls *iob = sd; + int rc; + + do + rc = gnutls_record_send (iob->sess, data, size); + while (rc == GNUTLS_E_INTERRUPTED || rc == GNUTLS_E_AGAIN); + if (rc >= 0) + { + if (nbytes) + *nbytes = rc; + return 0; + } + return rc; +} + +static const char * +iotls_strerror (void *sd, int rc) +{ + // struct iotls *iob = sd; + return gnutls_strerror (rc); +} + +static void +iotls_close (void *sd) +{ + struct iotls *iob = sd; + gnutls_bye (iob->sess, GNUTLS_SHUT_RDWR); + gnutls_deinit (iob->sess); +} + +static struct iodrv iotls_drv = { + iotls_read, + iotls_write, + iotls_close, + iotls_strerror +}; + +static gnutls_dh_params_t dh_params; +static gnutls_certificate_server_credentials x509_cred; + +static void +_tls_cleanup_x509 (void) +{ + if (x509_cred) + gnutls_certificate_free_credentials (x509_cred); +} + +#define DH_BITS 512 +static void +generate_dh_params (void) +{ + gnutls_dh_params_init (&dh_params); + gnutls_dh_params_generate2 (dh_params, DH_BITS); +} + +static int +tls_init (void) +{ + int rc; + + if (!enable_tls()) + return -1; + + gnutls_global_init (); + atexit (gnutls_global_deinit); + gnutls_certificate_allocate_credentials (&x509_cred); + atexit (_tls_cleanup_x509); + if (tls_cafile) + { + rc = gnutls_certificate_set_x509_trust_file (x509_cred, + tls_cafile, + GNUTLS_X509_FMT_PEM); + if (rc < 0) + { + terror ("%s: %s", tls_cafile, gnutls_strerror (rc)); + return -1; + } + } + + rc = gnutls_certificate_set_x509_key_file (x509_cred, + tls_cert, tls_key, + GNUTLS_X509_FMT_PEM); + if (rc < 0) + { + terror ("error reading certificate files: %s", gnutls_strerror (rc)); + return -1; + } + + generate_dh_params (); + gnutls_certificate_set_dh_params (x509_cred, dh_params); + + return 0; +} + +static ssize_t +_tls_fd_pull (gnutls_transport_ptr_t fd, void *buf, size_t size) +{ + struct iotls *bp = fd; + int rc; + do + { + rc = read (bp->fd[IO2_RD], buf, size); + } + while (rc == -1 && errno == EAGAIN); + return rc; +} + +static ssize_t +_tls_fd_push (gnutls_transport_ptr_t fd, const void *buf, size_t size) +{ + struct iotls *bp = fd; + int rc; + do + { + rc = write (bp->fd[IO2_WR], buf, size); + } + while (rc == -1 && errno == EAGAIN); + return rc; +} + +struct iobase * +iotls_create (int in, int out) +{ + struct iotls *bp = (struct iotls *) iobase_create (&iotls_drv, sizeof (*bp)); + int rc; + + bp->fd[IO2_RD] = in; + bp->fd[IO2_WR] = out; + gnutls_init (&bp->sess, GNUTLS_SERVER); + gnutls_set_default_priority (bp->sess); + gnutls_credentials_set (bp->sess, GNUTLS_CRD_CERTIFICATE, x509_cred); + gnutls_certificate_server_set_request (bp->sess, GNUTLS_CERT_REQUEST); + gnutls_dh_set_prime_bits (bp->sess, DH_BITS); + + gnutls_transport_set_pull_function (bp->sess, _tls_fd_pull); + gnutls_transport_set_push_function (bp->sess, _tls_fd_push); + + gnutls_transport_set_ptr2 (bp->sess, + (gnutls_transport_ptr_t) bp, + (gnutls_transport_ptr_t) bp); + rc = gnutls_handshake (bp->sess); + if (rc < 0) + { + gnutls_deinit (bp->sess); + gnutls_perror (rc); + free (bp); + return NULL; + } + + return (struct iobase *)bp; +} +#else +static inline int set_tls_opt (int c) { + terror ("option -%c not supported: program compiled without support for TLS", + c); + return 1; +} +static inline int enable_tls(void) { return 0; } +static inline int tls_init (void) { return -1; } +static inline struct iobase *iotls_create (int in, int out) { return NULL; } +#endif + +/* Two-way I/O */ +struct io2 +{ + struct iobase base; + struct iobase *iob[2]; +}; + +static int +io2_read (void *sd, char *data, size_t size, size_t *nbytes) +{ + struct io2 *iob = sd; + ssize_t n = iobase_readln (iob->iob[IO2_RD], data, size); + if (n < 0) + return -(1 + IO2_RD); + *nbytes = n; + return 0; +} + +static int +io2_write (void *sd, char *data, size_t size, size_t *nbytes) +{ + struct io2 *iob = sd; + ssize_t n = iobase_writeln (iob->iob[IO2_WR], data, size); + if (n < 0) + return -(1 + IO2_WR); + *nbytes = n; + return 0; +} + +static char const * +io2_strerror (void *sd, int rc) +{ + struct io2 *iob = sd; + int n = -rc - 1; + switch (n) + { + case IO2_RD: + case IO2_WR: + return iobase_strerror (iob->iob[n]); + + default: + return "undefined error"; + } +} + +static void +io2_close (void *sd) +{ + struct io2 *iob = sd; + iobase_close (iob->iob[IO2_RD]); + iobase_close (iob->iob[IO2_WR]); +} + +static struct iodrv io2_drv = { + io2_read, + io2_write, + io2_close, + io2_strerror +}; + +struct iobase * +io2_create (struct iobase *in, struct iobase *out) +{ + struct io2 *bp = (struct io2 *) iobase_create (&io2_drv, sizeof (*bp)); + bp->iob[IO2_RD] = in; + bp->iob[IO2_WR] = out; + return (struct iobase*) bp; +} + +/* SMTP implementation */ +enum smtp_state + { + STATE_ERR, // Reserved + STATE_INIT, + STATE_EHLO, + STATE_MAIL, + STATE_RCPT, + STATE_DATA, + STATE_QUIT, + MAX_STATE + }; + +#define MAX_RCPT 32 + +struct smtp +{ + enum smtp_state state; + struct iobase *iob; + char buf[IOBUFSIZE]; + char *arg; + int capa_mask; + char *helo; + char *sender; + char *rcpt[MAX_RCPT]; + int nrcpt; + char *data_buf; + size_t data_len; + size_t data_size; +}; + +static void +smtp_io_send (struct iobase *iob, int code, char *fmt, ...) +{ + va_list ap; + char buf[IOBUFSIZE]; + int n; + + snprintf (buf, sizeof buf, "%3d ", code); + va_start (ap, fmt); + n = vsnprintf (buf + 4, sizeof buf - 6, fmt, ap); + va_end (ap); + n += 4; + buf[n++] = '\r'; + buf[n++] = '\n'; + if (iobase_writeln (iob, buf, n) < 0) + { + terror ("iobase_writeln: %s", iobase_strerror (iob)); + exit (EX_FAILURE); + } +} + +static void +smtp_io_mlsend (struct iobase *iob, int code, char const **av) +{ + char buf[IOBUFSIZE]; + size_t n; + int i; + + snprintf (buf, sizeof buf, "%3d", code); + for (i = 0; av[i]; i++) + { + n = snprintf (buf, sizeof(buf), "%3d%c%s\r\n", + code, av[i+1] ? '-' : ' ', av[i]); + if (iobase_writeln (iob, buf, n) < 0) + { + terror ("iobase_writeln: %s", iobase_strerror (iob)); + exit (EX_FAILURE); + } + } +} + +static void +smtp_reset (struct smtp *smtp, int state) +{ + switch (state) + { + case STATE_INIT: + free (smtp->helo); + smtp->helo = NULL; + /* FALL THROUGH */ + case STATE_MAIL: + free (smtp->sender); + smtp->sender = NULL; + /* FALL THROUGH */ + case STATE_RCPT: + { + int i; + for (i = 0; i < smtp->nrcpt; i++) + free (smtp->rcpt[i]); + smtp->nrcpt = 0; + } + /* FALL THROUGH */ + case STATE_DATA: + free (smtp->data_buf); + smtp->data_buf = NULL; + smtp->data_len = 0; + smtp->data_size = 0; + } +} + +void +smtp_end (struct smtp *smtp) +{ + smtp_io_send (smtp->iob, 221, "Bye"); + iobase_close (smtp->iob); + smtp_reset (smtp, STATE_INIT); + smtp->iob = NULL; +} + +enum smtp_keyword + { + KW_HELP, + KW_RSET, + KW_EHLO, + KW_HELO, + KW_MAIL, + KW_RCPT, + KW_DATA, + KW_STARTTLS, + KW_QUIT, + MAX_KW + }; + +static char *smtp_keyword_trans[MAX_KW] = { + [KW_HELP] = "HELP", + [KW_RSET] = "RSET", + [KW_EHLO] = "EHLO", + [KW_HELO] = "HELO", + [KW_MAIL] = "MAIL", + [KW_RCPT] = "RCPT", + [KW_DATA] = "DATA", + [KW_STARTTLS] = "STARTTLS", + [KW_QUIT] = "QUIT" +}; + +static int +smtp_keyword_find (char const *kw) +{ + int i; + for (i = 0; i < MAX_KW; i++) + if (strcasecmp (kw, smtp_keyword_trans[i]) == 0) + return i; + return -1; +} + +static int +smtp_help (struct smtp *smtp) +{ + smtp_io_send (smtp->iob, 214, "http://www.ietf.org/rfc/rfc2821.txt"); + return 0; +} + +static int +smtp_rset (struct smtp *smtp) +{ + if (smtp->arg) + { + smtp_io_send (smtp->iob, 501, "rset does not take arguments"); + return -1; + } + smtp_io_send (smtp->iob, 250, "Reset state"); + + smtp_reset (smtp, STATE_INIT); + + return 0; +} + +enum + { + CAPA_PIPELINING, + CAPA_STARTTLS, + CAPA_HELP, + MAX_CAPA + }; + +static char const *capa_str[] = { + "PIPELINING", + "STARTTLS", + "HELP" +}; + +#define CAPA_MASK(n) (1<<(n)) + +static int +smtp_ehlo (struct smtp *smtp) +{ + char const *capa[MAX_CAPA+2]; + int i, j; + + if (!smtp->arg) + { + smtp_io_send (smtp->iob, 501, "ehlo requires domain address"); + return -1; + } + + capa[0] = "localhost Mock MTA pleased to meet you"; + for (i = 0, j = 1; i < MAX_CAPA; i++) + if (!(smtp->capa_mask & CAPA_MASK (i))) + capa[j++] = capa_str[i]; + capa[j] = NULL; + smtp_io_mlsend (smtp->iob, 250, capa); + + smtp_reset (smtp, STATE_INIT); + if ((smtp->helo = strdup (smtp->arg)) == NULL) + nomemory (); + return 0; +} + +static int +smtp_starttls (struct smtp *smtp) +{ + struct io2 *orig_iob = (struct io2 *) smtp->iob; + struct iofile *inb = (struct iofile *)orig_iob->iob[IO2_RD]; + struct iofile *outb = (struct iofile *)orig_iob->iob[IO2_WR]; + struct iobase *iob; + + if (smtp->arg) + { + smtp_io_send (smtp->iob, 501, "Syntax error (no parameters allowed)"); + return -1; + } + + smtp_io_send (smtp->iob, 220, "Ready to start TLS"); + + iob = iotls_create (inb->fd, outb->fd); + + if (iob) + { + free (inb); + free (outb); + free (orig_iob); + smtp->iob = iob; + disable_starttls (); + smtp->capa_mask |= CAPA_MASK (CAPA_STARTTLS); + } + else + { + free (iob); + exit (EX_FAILURE); + } + return 0; +} + +static int +smtp_quit (struct smtp *smtp) +{ + return 0; +} + +static int +smtp_helo (struct smtp *smtp) +{ + if (!smtp->arg) + { + smtp_io_send (smtp->iob, 501, "helo requires domain address"); + return -1; + } + smtp_io_send (smtp->iob, 250, "localhost Mock MTA pleased to meet you"); + + smtp_reset (smtp, STATE_INIT); + if ((smtp->helo = strdup (smtp->arg)) == NULL) + nomemory (); + return 0; +} + +static int +smtp_mail (struct smtp *smtp) +{ + static char from_str[] = "FROM:"; + static size_t from_len = sizeof(from_str) - 1; + char *p; + + if (!smtp->arg) + { + smtp_io_send (smtp->iob, 501, "mail requires email address"); + return -1; + } + if (strncasecmp (smtp->arg, from_str, from_len)) + { + smtp_io_send (smtp->iob, 501, "syntax error"); + return -1; + } + p = smtp->arg + from_len; + while (*p && (*p == ' ' || *p == '\t')) + p++; + if (!*p) + { + smtp_io_send (smtp->iob, 501, "mail requires email address"); + return -1; + } + smtp_reset (smtp, STATE_MAIL); + if ((smtp->sender = strdup (p)) == NULL) + nomemory (); + smtp_io_send (smtp->iob, 250, "Sender ok"); + return 0; +} + +static int +smtp_rcpt (struct smtp *smtp) +{ + static char to_str[] = "TO:"; + static size_t to_len = sizeof(to_str) - 1; + char *p; + + if (!smtp->arg) + { + smtp_io_send (smtp->iob, 501, "rcpt requires email address"); + return -1; + } + if (strncasecmp (smtp->arg, to_str, to_len)) + { + smtp_io_send (smtp->iob, 501, "syntax error"); + return -1; + } + p = smtp->arg + to_len; + while (*p && (*p == ' ' || *p == '\t')) + p++; + if (!*p) + { + smtp_io_send (smtp->iob, 501, "to requires email address"); + return -1; + } + if (smtp->nrcpt == MAX_RCPT) + { + smtp_io_send (smtp->iob, 501, "too many recipients"); + return -1; + } + if ((smtp->rcpt[smtp->nrcpt] = strdup (p)) == NULL) + nomemory (); + smtp->nrcpt++; + smtp_io_send (smtp->iob, 250, "Recipient ok"); + return 0; +} + +static void +smtp_data_save (struct smtp *smtp) +{ + size_t len = strlen (smtp->buf); + while (smtp->data_len + len > smtp->data_size) + { + char *p; + size_t n = smtp->data_size; + if (!smtp->data_buf) + { + n = smtp->data_len + len; + } + else + { + if ((size_t)-1 / 3 * 2 <= n) + nomemory (); + n += (n + 1) / 2; + } + p = realloc (smtp->data_buf, n); + if (!p) + nomemory (); + smtp->data_buf = p; + smtp->data_size = n; + } + memcpy (smtp->data_buf + smtp->data_len, smtp->buf, len); + smtp->data_len += len; +} + +static int +begins_with_from (char const *p) +{ + while (*p == '>') + p++; + return strncmp (p, "From", 4) == 0; +} + +static int +smtp_data (struct smtp *smtp) +{ + ssize_t n; + int i; + int fd; + FILE *fp; + struct flock lk; + time_t t; + int in_body = 0; + off_t length; + int res = 0; + + /* Open the mailbox */ + fd = open (mailbox_name, O_CREAT|O_WRONLY|O_APPEND, 0600); + if (fd == -1) + { + terror ("can't open %s: %s", mailbox_name, strerror (errno)); + smtp_io_send (smtp->iob, 451, "Local filesystem error"); + return -1; + } + lk.l_type = F_WRLCK; + lk.l_whence = SEEK_END; + lk.l_start = 0; + lk.l_len = 0; + if (fcntl (fd, F_SETLKW, &lk)) /* FIXME: ttl */ + { + terror ("can't lock %s: %s", mailbox_name, strerror (errno)); + smtp_io_send (smtp->iob, 451, "Local filesystem error"); + close (fd); + return -1; + } + + length = lseek (fd, 0, SEEK_END); + + fp = fdopen (fd, "a"); + if (!fp) + { + terror ("fdopen: %s", strerror (errno)); + smtp_io_send (smtp->iob, 451, "Local filesystem error"); + close (fd); + return -1; + } + + smtp_io_send (smtp->iob, 354, + "Enter mail, end with \".\" on a line by itself"); + + t = time (NULL); + fprintf (fp, "From %s %s", smtp->sender, asctime (gmtime (&t))); + + while (1) + { + char *p; + n = iobase_readln (smtp->iob, smtp->buf, sizeof (smtp->buf)); + if (n <= 0) + { + smtp->state = STATE_QUIT; + if (smtp->iob->iob_eof) + terror ("unexpected end of file"); + else + terror ("read error: %s", strerror (smtp->iob->iob_errno)); + res = 1; + break; + } + + if (smtp->buf[n-1] == '\n') + { + if (n > 1 && smtp->buf[n-2] == '\r') + { + smtp->buf[n-2] = '\n'; + smtp->buf[n-1] = 0; + n--; + } + } + else + { + terror ("line too long"); + break; + } + + if (n == 1 && !in_body) + in_body = 1; + + p = smtp->buf; + if (in_body) + { + if (*p == '.') + { + if (p[1] == '\n') + break; + if (p[1] == '.') + { + p++; + n--; + } + } + else if (begins_with_from (p)) + fputc ('>', fp); + } + fwrite (p, n, 1, fp); + } + fputc ('\n', fp); + + if (res) + { + fflush (fp); + ftruncate (fd, length); + } + + lk.l_type = F_UNLCK; + lk.l_whence = SEEK_SET; + lk.l_start = 0; + lk.l_len = 0; + if (fcntl (fd, F_SETLK, &lk)) + terror ("can't unlock %s: %s", mailbox_name, strerror (errno)); + fclose (fp); + + smtp_io_send (smtp->iob, 250, "%04d Message accepted for delivery", msgid); + msgid++; + return res; +} + + +struct smtp_transition +{ + int new_state; + int (*handler) (struct smtp *); +}; + +static struct smtp_transition smtp_transition_table[MAX_STATE][MAX_KW] = { + [STATE_INIT] = { + [KW_HELP] = { STATE_INIT, smtp_help }, + [KW_RSET] = { STATE_INIT, smtp_rset }, + [KW_HELO] = { STATE_EHLO, smtp_helo }, + [KW_EHLO] = { STATE_EHLO, smtp_ehlo }, + [KW_QUIT] = { STATE_QUIT, smtp_quit } + }, + [STATE_EHLO] = { + [KW_HELP] = { STATE_EHLO, smtp_help }, + [KW_RSET] = { STATE_INIT, smtp_rset }, + [KW_HELO] = { STATE_EHLO, smtp_helo }, + [KW_EHLO] = { STATE_EHLO, smtp_ehlo }, + [KW_MAIL] = { STATE_MAIL, smtp_mail }, + [KW_STARTTLS] = { STATE_EHLO, smtp_starttls }, + [KW_QUIT] = { STATE_QUIT, smtp_quit } + }, + [STATE_MAIL] = { + [KW_HELP] = { STATE_MAIL, smtp_help }, + [KW_RSET] = { STATE_INIT, smtp_rset }, + [KW_RCPT] = { STATE_RCPT, smtp_rcpt }, + [KW_HELO] = { STATE_EHLO, smtp_helo }, + [KW_EHLO] = { STATE_EHLO, smtp_ehlo }, + [KW_QUIT] = { STATE_QUIT, smtp_quit } + }, + [STATE_RCPT] = { + [KW_HELP] = { STATE_RCPT, smtp_help }, + [KW_RSET] = { STATE_INIT, smtp_rset }, + [KW_RCPT] = { STATE_RCPT, smtp_rcpt }, + [KW_HELO] = { STATE_EHLO, smtp_helo }, + [KW_EHLO] = { STATE_EHLO, smtp_ |