summaryrefslogtreecommitdiff
path: root/testsuite/mockmail.c
diff options
context:
space:
mode:
Diffstat (limited to 'testsuite/mockmail.c')
-rw-r--r--testsuite/mockmail.c506
1 files changed, 506 insertions, 0 deletions
diff --git a/testsuite/mockmail.c b/testsuite/mockmail.c
new file mode 100644
index 000000000..70c3b781f
--- /dev/null
+++ b/testsuite/mockmail.c
@@ -0,0 +1,506 @@
+/*
+ NAME
+ mockmail - mock Sendmail binary for use in test suites
+
+ SYNOPSIS
+ mockmail [-bm] [-f EMAIL] [-itv] [-oi] [EMAIL ...]
+
+ DESCRIPTION
+ Mimicks the behavior of "sendmail -bm". Instead of delivering
+ the message, dumps it to the file "mail.dump". The location of
+ the dump file can be changed by setting the MAIL_DUMP environment
+ variable.
+
+ Being a mailutils test tool, mockname is written without relying on
+ the mailutils libraries. Only libc is needed.
+
+ Message to be "delivered" is read from the standard input.
+
+ Only rudimentary message parsing is performed, as necessary for
+ the functioning of the -t option. Similarly, email address parser
+ is very naive.
+
+ OPTIONS
+ -bm Ignored for compatibility with Sendmail.
+ -f EMAIL Sets sender email address.
+ -i Same as -oi.
+ -t Read recipients from the message.
+ -oi Don't expect the incoming message to be terminated with
+ a dot. This also turns off dot unstuffing.
+ -v Ignored for compatibility with Sendmail.
+
+ ENVIRONMENT
+
+ MAIL_DUMP
+ Name of the dump file ("mail.dump")
+
+ MAIL_DUMP_APPEND
+ When set, append new entries to the mail.dump file, instead of
+ overwriting it.
+
+ DUMP FORMAT
+ The message is represented as a series of records:
+
+ MSGID: 0001
+ This record is for compatibility with mockmta.
+ SENDER: <string>
+ Sender email address as given by the -f option, if given.
+ NRCPT: <numeric>
+ Number of recipients.
+
+ The list of recipients follows this line. Each record in the list is
+
+ RCPT[<I>]: <string>
+
+ where <I> is 0-based index of the recipient in recipient table.
+
+ LENGTH: <N>
+ Total length of the data section, after eventual dot-unstuffing.
+ This does not include terminating dot (if such was present and
+ the -oi option was not given).
+
+ This line is followed by <N> bytes representing the material received
+ from the standard input.
+
+ Message dump is terminated by a single LF character.
+
+ BUGS
+ At most 32 recipients are allowed.
+ Header and address parsing is rudimentary.
+
+ AUTHOR
+ Sergey Poznyakoff <gray@gnu.org>
+
+ LICENSE
+ This program is part of GNU Mailutils testsuite.
+ Copyright (C) 2020-2024 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/>.
+
+*/
+#include <config.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+char *progname;
+FILE *logfile;
+
+char *from_person = NULL; /* Set the name of the `from' person */
+int read_recipients = 0; /* Read the message for recipients */
+int dot = 1; /* Message is terminated by a lone dot on a line */
+
+#define MAXRCPT 32
+
+enum
+ {
+ EX_OK,
+ EX_FAILURE,
+ EX_USAGE
+ };
+
+static void
+terror (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);
+
+ fprintf (stderr, "%s: ", progname);
+ va_start (ap, fmt);
+ vfprintf (stderr, fmtbuf, ap);
+ va_end (ap);
+ fputc ('\n', stderr);
+}
+
+static void
+nomemory (void)
+{
+ terror ("out of memory");
+ exit (EX_FAILURE);
+}
+
+static inline int
+is_blank (int c)
+{
+ return c == ' ' || c == '\t';
+}
+
+enum { H_INIT, H_HEADER, H_NL, B_INIT, B_NL, B_DOT };
+
+struct header_map
+{
+ struct header_map *prev, *next; /* List of elements */
+ size_t hstart; /* Header start offset */
+ size_t nlen; /* Header name length */
+ size_t vstart; /* Value start offset */
+ size_t end; /* Header end offset (points past the final LF) */
+};
+
+struct message
+{
+ char *buf_ptr;
+ size_t buf_len;
+ size_t buf_size;
+ size_t line_len;
+
+ char *rcpt[MAXRCPT];
+ int nrcpt;
+
+ size_t header_len;
+ struct header_map *header_head, *header_tail;
+
+ int state;
+};
+
+static void
+msg_header_add (struct message *msg)
+{
+ struct header_map *hmap;
+ char *hdr;
+ size_t i;
+
+ hmap = calloc (1, sizeof hmap[0]);
+ if (!hmap)
+ nomemory ();
+ hmap->hstart = msg->buf_len - msg->line_len;
+ hdr = msg->buf_ptr + hmap->hstart;
+ for (i = msg->line_len - 1; i > 0; i--)
+ if (!is_blank (hdr[i-1]))
+ break;
+ hmap->nlen = i;
+ hmap->vstart = msg->buf_len;
+ hmap->next = NULL;
+ hmap->prev = msg->header_tail;
+ if (msg->header_tail)
+ msg->header_tail->next = hmap;
+ else
+ msg->header_head = hmap;
+ msg->header_tail = hmap;
+}
+
+static void
+msg_header_update (struct message *msg)
+{
+ struct header_map *hmap = msg->header_tail;
+ hmap->end = msg->buf_len;
+ while (hmap->vstart < hmap->end && is_blank (msg->buf_ptr[hmap->vstart]))
+ hmap->vstart++;
+}
+
+static void
+msg_add_rcpt (struct message *msg, char const *email)
+{
+ if (msg->nrcpt == MAXRCPT)
+ {
+ terror ("too many recipients");
+ exit (EX_USAGE);
+ }
+ if ((msg->rcpt[msg->nrcpt] = strdup (email)) == NULL)
+ nomemory ();
+ msg->nrcpt++;
+}
+
+static void
+msg_add_char (struct message *msg, int c)
+{
+ while (msg->buf_len + 1 > msg->buf_size)
+ {
+ char *p;
+ size_t n = msg->buf_size;
+ if (!msg->buf_ptr)
+ {
+ n = 64;
+ }
+ else
+ {
+ if ((size_t)-1 / 3 * 2 <= n)
+ nomemory ();
+ n += (n + 1) / 2;
+ }
+ p = realloc (msg->buf_ptr, n);
+ if (!p)
+ nomemory ();
+ msg->buf_ptr = p;
+ msg->buf_size = n;
+ }
+ msg->buf_ptr[msg->buf_len++] = c;
+ if (c == '\n')
+ msg->line_len = 0;
+ else
+ msg->line_len++;
+}
+
+int
+main (int argc, char **argv)
+{
+ struct message msg;
+ int c;
+ char *filename;
+ struct header_map *hmap;
+
+ progname = argv[0];
+
+ while ((c = getopt (argc, argv, "b:f:ito:v")) != EOF)
+ {
+ switch (c)
+ {
+ case 'b':
+ if (strcmp (optarg, "m"))
+ {
+ terror ("-b%s not supported", optarg);
+ exit (EX_USAGE);
+ }
+ break;
+
+ case 'f':
+ from_person = optarg;
+ break;
+
+ case 'i':
+ dot = 0;
+ break;
+
+ case 't':
+ read_recipients = 1;
+ break;
+
+ case 'o':
+ switch (optarg[0])
+ {
+ case 'i':
+ dot = 0;
+ break;
+
+ default:
+ /* IGNORED */ ;
+ }
+ break;
+
+ case 'v':
+ break;
+
+ default:
+ exit (EX_USAGE);
+ }
+ }
+
+ memset (&msg, 0, sizeof (msg));
+
+ for (c = optind; c < argc; c++)
+ msg_add_rcpt (&msg, argv[c]);
+
+ msg.state = H_INIT;
+
+ while ((c = getchar ()) != EOF)
+ {
+ msg_add_char (&msg, c);
+ switch (msg.state)
+ {
+ case H_INIT:
+ if (c == ':')
+ {
+ msg_header_add (&msg);
+ msg.state = H_HEADER;
+ }
+ break;
+
+ case H_HEADER:
+ if (c == '\n')
+ msg.state = H_NL;
+ break;
+
+ case H_NL:
+ if (is_blank (c))
+ msg.state = H_HEADER;
+ else
+ {
+ msg_header_update (&msg);
+ if (c == '\n')
+ {
+ msg.header_len = msg.buf_len;
+ msg.state = B_INIT;
+ }
+ else
+ msg.state = H_INIT;
+ }
+ break;
+
+ case B_INIT:
+ if (c == '\n')
+ msg.state = B_NL;
+ break;
+
+ case B_NL:
+ if (c == '.')
+ msg.state = B_DOT;
+ else
+ msg.state = B_INIT;
+ break;
+
+ case B_DOT:
+ if (c == '\n')
+ {
+ if (dot)
+ goto end;
+ msg.state = B_NL;
+ }
+ else
+ {
+ if (c == '.' && dot)
+ /* unstuff */
+ msg.buf_len--;
+ msg.state = B_INIT;
+ }
+ break;
+ }
+ }
+ end:
+
+ switch (msg.state)
+ {
+ case H_INIT:
+ case H_HEADER:
+ terror ("malformed message");
+ break;
+
+ case B_DOT:
+ msg.buf_len -= 2;
+ break;
+
+ default:
+ if (dot)
+ terror ("missing terminating dot");
+ }
+
+ if (read_recipients)
+ {
+ for (hmap = msg.header_head; hmap; hmap = hmap->next)
+ {
+ char *p = msg.buf_ptr + hmap->hstart;
+ if ((hmap->nlen == 2 && strncasecmp (p, "to", 2) == 0) ||
+ (hmap->nlen == 2 && strncasecmp (p, "cc", 2) == 0) ||
+ (hmap->nlen == 3 && strncasecmp (p, "bcc", 3) == 0))
+ {
+ char *cp;
+ size_t i, j;
+ size_t len = hmap->end - hmap->vstart;
+ int unwrap = 0;
+
+ p = msg.buf_ptr + hmap->vstart;
+
+ /* Allocate temporary value copy */
+ cp = malloc (len + 1);
+ if (!p)
+ nomemory ();
+ /* Unwrap the value */
+ for (i = j = 0; i < len; i++)
+ {
+ if (p[i] == '\n')
+ unwrap = 1;
+ else if (unwrap)
+ {
+ unwrap = 0;
+ continue;
+ }
+ else
+ cp[j++] = p[i];
+ }
+ cp[j] = 0;
+
+ /* A *very* naive and simplified address parser */
+ for (p = strtok (cp, ","); p; p = strtok (NULL, ","))
+ {
+ char *q = strchr (p, '<');
+ if (q)
+ {
+ p = strchr (q, '>');
+ if (p)
+ p[1] = 0;
+ msg_add_rcpt (&msg, q);
+ }
+ else
+ msg_add_rcpt (&msg, p);
+ }
+ free (cp);
+ }
+ }
+ }
+
+ if (msg.nrcpt == 0)
+ terror ("no recipients");
+
+ filename = getenv ("MAIL_DUMP");
+ if (!filename)
+ filename = "mail.dump";
+
+ logfile = fopen (filename, getenv ("MAIL_DUMP_APPEND") ? "a" : "w");
+ if (!logfile)
+ {
+ terror ("can't open dump file %s: %m", filename);
+ exit (EX_FAILURE);
+ }
+
+ fprintf (logfile, "MSGID: %04d\n", 1);
+ if (from_person)
+ fprintf (logfile, "SENDER: %s\n", from_person);
+ fprintf (logfile, "NRCPT: %d\n", msg.nrcpt);
+ for (c = 0; c < msg.nrcpt; c++)
+ fprintf (logfile, "RCPT[%d]: %s\n", c, msg.rcpt[c]);
+ fprintf (logfile, "LENGTH: %lu\n", (unsigned long)msg.buf_len);
+ fwrite (msg.buf_ptr, msg.buf_len, 1, logfile);
+ fputc ('\n', logfile);
+ fclose (logfile);
+ exit (EX_OK);
+}

Return to:

Send suggestions and report system problems to the System administrator.