A new test program, fakesmtp, to help us test post.
authorKen Hornstein <kenh@pobox.com>
Wed, 14 Mar 2012 19:51:22 +0000 (15:51 -0400)
committerKen Hornstein <kenh@pobox.com>
Wed, 14 Mar 2012 19:51:22 +0000 (15:51 -0400)
.gitignore
Makefile.am
test/fakesmtp.c [new file with mode: 0644]

index d1dd750..9031616 100644 (file)
@@ -96,6 +96,7 @@ a.out.DSYM/
 /uip/whatnow
 /uip/whom
 /uip/*.exe
+/test/fakesmtp
 /test/getfullname
 /test/getfqdn
 
index 9ca7740..e402ca1 100644 (file)
@@ -65,7 +65,7 @@ TESTS = test/bad-input/test-header \
        test/cleanup ## The "cleanup" test should always be last.
 
 check_SCRIPTS = test/common.sh
-check_PROGRAMS = test/getfullname test/getfqdn
+check_PROGRAMS = test/getfullname test/getfqdn test/fakesmtp
 
 ##
 ## Stuff that should be cleaned via "make clean"
@@ -366,6 +366,9 @@ test_getfullname_LDADD =
 test_getfqdn_SOURCES = test/getfqdn.c
 test_getfqdn_LDADD =
 
+test_fakesmtp_SOURCES = test/fakesmtp.c
+test_fakesmtp_LDADD =
+
 ##
 ## Our rebuild rules for files that aren't built via the normal mechanisms
 ##
diff --git a/test/fakesmtp.c b/test/fakesmtp.c
new file mode 100644 (file)
index 0000000..19df2e9
--- /dev/null
@@ -0,0 +1,310 @@
+/*
+ * fakesmtp - A fake SMTP server used by the nmh test suite
+ *
+ * This code is Copyright (c) 2012, by the authors of nmh.  See the
+ * COPYRIGHT file in the root directory of the nmh distribution for
+ * complete copyright information.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <signal.h>
+
+#define PIDFILE "/tmp/fakesmtp.pid"
+
+#define LINESIZE 1024
+
+static void killpidfile(void);
+static void handleterm(int);
+static void putsmtp(int, char *);
+static int getsmtp(int, char *);
+
+int
+main(int argc, char *argv[])
+{
+       struct addrinfo hints, *res;
+       int rc, l, conn, on, datamode;
+       FILE *f, *pid;
+       fd_set readfd;
+       struct stat st;
+       struct timeval tv;
+
+       if (argc != 3) {
+               fprintf(stderr, "Usage: %s output-filename port\n", argv[0]);
+               exit(1);
+       }
+
+       if (!(f = fopen(argv[1], "w"))) {
+               fprintf(stderr, "Unable to open output file \"%s\": %s\n",
+                       argv[1], strerror(errno));
+               exit(1);
+       }
+
+       memset(&hints, 0, sizeof(hints));
+
+       hints.ai_family = PF_INET;
+       hints.ai_socktype = SOCK_STREAM;
+       hints.ai_protocol = IPPROTO_TCP;
+       hints.ai_flags = AI_PASSIVE;
+
+       rc = getaddrinfo("127.0.0.1", argv[2], &hints, &res);
+
+       if (rc) {
+               fprintf(stderr, "Unable to resolve localhost/%s: %s\n",
+                       argv[2], gai_strerror(rc));
+               exit(1);
+       }
+
+       l = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+
+       if (l == -1) {
+               fprintf(stderr, "Unable to create listening socket: %s\n",
+                       strerror(errno));
+               exit(1);
+       }
+
+       on = 1;
+
+       if (setsockopt(l, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
+               fprintf(stderr, "Unable to set SO_REUSEADDR: %s\n",
+                       strerror(errno));
+               exit(1);
+       }
+
+       if (bind(l, res->ai_addr, res->ai_addrlen) == -1) {
+               fprintf(stderr, "Unable to bind socket: %s\n", strerror(errno));
+               exit(1);
+       }
+
+       if (listen(l, 1) == -1) {
+               fprintf(stderr, "Unable to listen on socket: %s\n",
+                       strerror(errno));
+               exit(1);
+       }
+
+       /*
+        * Now that our socket & files are set up, do the following things:
+        *
+        * - Check for a PID file.  If there is one, kill the old version.
+        * - Wait 30 seconds for a connection.  If there isn't one, then
+        *   exit.
+        */
+
+       if (stat(PIDFILE, &st) == 0) {
+               long oldpid;
+
+               if (!(pid = fopen(PIDFILE, "r"))) {
+                       fprintf(stderr, "Cannot open " PIDFILE
+                               " (%s), continuing ...\n", strerror(errno));
+               } else {
+                       rc = fscanf(pid, "%ld", &oldpid);
+                       fclose(pid);
+
+                       if (rc != 1) {
+                               fprintf(stderr, "Unable to parse pid in "
+                                       PIDFILE ", continuing ...\n");
+                       } else {
+                               kill((pid_t) oldpid, SIGTERM);
+                       }
+               }
+
+               unlink(PIDFILE);
+       }
+
+       if (!(pid = fopen(PIDFILE, "w"))) {
+               fprintf(stderr, "Cannot open " PIDFILE ": %s\n",
+                       strerror(errno));
+               exit(1);
+       }
+
+       fprintf(pid, "%ld\n", (long) getpid());
+       fclose(pid);
+
+       signal(SIGTERM, handleterm);
+       atexit(killpidfile);
+
+       FD_ZERO(&readfd);
+       FD_SET(l, &readfd);
+       tv.tv_sec = 30;
+       tv.tv_usec = 0;
+
+       rc = select(l + 1, &readfd, NULL, NULL, &tv);
+
+       if (rc < 0) {
+               fprintf(stderr, "select() failed: %s\n", strerror(errno));
+               exit(1);
+       }
+
+       /*
+        * I think if we get a timeout, we should just exit quietly.
+        */
+
+       if (rc == 0) {
+               exit(1);
+       }
+
+       /*
+        * Alright, got a connection!  Accept it.
+        */
+
+       if ((conn = accept(l, NULL, NULL)) == -1) {
+               fprintf(stderr, "Unable to accept connection: %s\n",
+                       strerror(errno));
+               exit(1);
+       }
+
+       close(l);
+       
+       /*
+        * Pretend to be an SMTP server.
+        */
+
+       putsmtp(conn, "220 Not really an ESMTP server");
+       datamode = 0;
+
+       for (;;) {
+               char line[LINESIZE];
+
+               rc = getsmtp(conn, line);
+
+               if (rc == -1)
+                       break;  /* EOF */
+
+               fprintf(f, "%s\n", line);
+
+               /*
+                * If we're in DATA mode, then check to see if we've got
+                * a "."; otherwise, continue
+                */
+
+               if (datamode) {
+                       if (strcmp(line, ".") == 0) {
+                               datamode = 0;
+                               putsmtp(conn, "250 Thanks for the info!");
+                       }
+                       continue;
+               }
+
+               /*
+                * Most commands we ignore and send the same response to.
+                */
+
+               if (strcmp(line, "QUIT") == 0) {
+                       putsmtp(conn, "221 Later alligator!");
+                       close(conn);
+                       break;
+               } else if (strcmp(line, "DATA") == 0) {
+                       putsmtp(conn, "354 Go ahead");
+                       datamode = 1;
+               } else {
+                       putsmtp(conn, "250 I'll buy that for a dollar!");
+               }
+       }
+
+       fclose(f);
+
+       exit(0);
+}
+
+/*
+ * Write a line to the SMTP client on the other end
+ */
+
+static void
+putsmtp(int socket, char *data)
+{
+       struct iovec iov[2];
+
+       iov[0].iov_base = data;
+       iov[0].iov_len = strlen(data);
+       iov[1].iov_base = "\r\n";
+       iov[1].iov_len = 2;
+
+       writev(socket, iov, 2);
+}
+
+/*
+ * Read a line (up to the \r\n)
+ */
+
+static int
+getsmtp(int socket, char *data)
+{
+       int cc;
+       static int bytesinbuf = 0;
+       static char buffer[LINESIZE * 2], *p;
+
+       for (;;) {
+               /*
+                * Find our \r\n
+                */
+
+               if (bytesinbuf > 0 && (p = strchr(buffer, '\r')) &&
+                                                       *(p + 1) == '\n') {
+                       *p = '\0';
+                       strncpy(data, buffer, LINESIZE);
+                       data[LINESIZE - 1] = '\0';
+                       cc = strlen(buffer);
+
+                       /*
+                        * Shuffle leftover bytes back to the beginning
+                        */
+
+                       bytesinbuf -= cc + 2;   /* Don't forget \r\n */
+                       if (bytesinbuf > 0) {
+                               memmove(buffer, buffer + cc + 2, bytesinbuf);
+                       }
+                       return cc;
+               }
+
+               if (bytesinbuf >= sizeof(buffer)) {
+                       fprintf(stderr, "Buffer overflow in getsmtp()!\n");
+                       exit(1);
+               }
+
+               memset(buffer + bytesinbuf, 0, sizeof(buffer) - bytesinbuf);
+               cc = read(socket, buffer + bytesinbuf,
+                         sizeof(buffer) - bytesinbuf);
+
+               if (cc < 0) {
+                       fprintf(stderr, "Read failed: %s\n", strerror(errno));
+                       exit(1);
+               }
+
+               if (cc == 0)
+                       return -1;
+
+               bytesinbuf += cc;
+       }
+}
+
+/*
+ * Handle a SIGTERM
+ */
+
+static void
+handleterm(int signal)
+{
+       killpidfile();
+       fflush(NULL);
+       _exit(1);
+}
+
+/*
+ * Get rid of our pid file
+ */
+
+static void
+killpidfile(void)
+{
+       unlink(PIDFILE);
+}