A new test for the POP support in inc. Very simple for now, but it seems
authorKen Hornstein <kenh@pobox.com>
Sun, 25 Nov 2012 05:49:04 +0000 (00:49 -0500)
committerKen Hornstein <kenh@pobox.com>
Sun, 25 Nov 2012 05:50:01 +0000 (00:50 -0500)
to at least check the basic functionality.

.gitignore
Makefile.am
test/fakepop.c [new file with mode: 0644]
test/inc/test-pop [new file with mode: 0755]

index 402f0cc..b6e7ab3 100644 (file)
@@ -97,6 +97,7 @@ a.out.dSYM/
 /uip/whom
 /uip/*.exe
 /test/fakesmtp
+/test/fakepop
 /test/getfullname
 /test/getfqdn
 
index 40bae41..6226054 100644 (file)
@@ -55,6 +55,7 @@ TESTS = test/ali/test-ali test/anno/test-anno \
        test/forw/test-forw-digest test/forw/test-forw-format \
        test/inc/test-deb359167 test/inc/test-eom-align \
        test/inc/test-inc-scanout test/inc/test-msgchk \
+       test/inc/test-pop \
        test/install-mh/test-install-mh test/manpages/test-manpages \
        test/mhbuild/test-forw test/mhbuild/test-utf8-body \
        test/mhlist/test-mhlist test/mhmail/test-mhmail \
@@ -81,7 +82,7 @@ TESTS = test/ali/test-ali test/anno/test-anno \
        test/cleanup ## The "cleanup" test should always be last.
 
 check_SCRIPTS = test/common.sh
-check_PROGRAMS = test/getfullname test/getfqdn test/fakesmtp
+check_PROGRAMS = test/getfullname test/getfqdn test/fakepop test/fakesmtp
 DISTCHECK_CONFIGURE_FLAGS = DISABLE_SETGID_MAIL=1
 
 ##
@@ -394,6 +395,9 @@ test_getfullname_LDADD = sbr/libmh.a
 test_getfqdn_SOURCES = test/getfqdn.c
 test_getfqdn_LDADD =
 
+test_fakepop_SOURCES = test/fakepop.c
+test_fakepop_LDADD =
+
 test_fakesmtp_SOURCES = test/fakesmtp.c
 test_fakesmtp_LDADD =
 
diff --git a/test/fakepop.c b/test/fakepop.c
new file mode 100644 (file)
index 0000000..7ff62a8
--- /dev/null
@@ -0,0 +1,414 @@
+/*
+ * fakepop - A fake POP 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 <limits.h>
+#include <signal.h>
+
+#define PIDFILE "/tmp/fakepop.pid"
+#define LINESIZE 1024
+#define BUFALLOC 4096
+
+#define CHECKUSER()    if (!user) { \
+                               putpop(s, "-ERR Aren't you forgetting " \
+                                      "something?  Like the USER command?"); \
+                               continue; \
+                       }
+#define CHECKUSERPASS()        CHECKUSER() \
+                       if (! pass) { \
+                               putpop(s, "-ERR Um, hello?  Forget to " \
+                                      "log in?"); \
+                               continue; \
+                       }
+
+static void putpop(int, char *);
+static void putpopbulk(int, char *);
+static int getpop(int, char *, ssize_t);
+static char *readmessage(FILE *);
+
+int
+main(int argc, char *argv[])
+{
+       struct addrinfo hints, *res;
+       struct stat st;
+       FILE *f, *pid;
+       char line[LINESIZE];
+       fd_set readfd;
+       struct timeval tv;
+       pid_t child;
+       int octets = 0, rc, l, s, on, user = 0, pass = 0, deleted = 0;
+
+       if (argc != 5) {
+               fprintf(stderr, "Usage: %s mail-file port username "
+                       "password\n", argv[0]);
+               exit(1);
+       }
+
+       if (!(f = fopen(argv[1], "r"))) {
+               fprintf(stderr, "Unable to open message file \"%s\": %s\n",
+                       argv[1], strerror(errno));
+               exit(1);
+       }
+
+       /*
+        * POP wants the size of the maildrop in bytes, but with \r\n line
+        * endings.  Calculate that.
+        */
+
+       while (fgets(line, sizeof(line), f)) {
+               octets += strlen(line);
+               if (strrchr(line, '\n'))
+                       octets++;
+       }
+
+       rewind(f);
+
+       /*
+        * If there is a pid file around, kill the previously running
+        * fakepop process.
+        */
+
+       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);
+       }
+
+       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);
+       }
+
+       /*
+        * Fork off a copy of ourselves, print out our child pid, then
+        * exit.
+        */
+
+       switch (child = fork()) {
+       case -1:
+               fprintf(stderr, "Unable to fork child: %s\n", strerror(errno));
+               exit(1);
+               break;
+       case 0:
+               /*
+                * Close stdin and stdout so $() in the shell will get an
+                * EOF.  For now leave stderr open.
+                */
+               fclose(stdin);
+               fclose(stdout);
+               break;
+       default:
+               printf("%ld\n", (long) child);
+               exit(0);
+       }
+
+       /*
+        * Now that our socket and files are set up, wait 30 seconds for
+        * a connection.  If there isn't one, then exit.
+        */
+
+       if (!(pid = fopen(PIDFILE, "w"))) {
+               fprintf(stderr, "Cannot open " PIDFILE ": %s\n",
+                       strerror(errno));
+               exit(1);
+       }
+
+       fprintf(pid, "%ld\n", (long) getpid());
+       fclose(pid);
+
+       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);
+       }
+
+       /*
+        * If we get a timeout, just silently exit
+        */
+
+       if (rc == 0) {
+               exit(1);
+       }
+
+       /*
+        * We got a connection; accept it.  Right after that close our
+        * listening socket so we won't get any more connections on it.
+        */
+
+       if ((s = accept(l, NULL, NULL)) == -1) {
+               fprintf(stderr, "Unable to accept connection: %s\n",
+                       strerror(errno));
+               exit(1);
+       }
+
+       close(l);
+
+       /*
+        * Pretend to be a POP server
+        */
+
+       putpop(s, "+OK Not really a POP server, but we play one on TV");
+
+       for (;;) {
+               char linebuf[LINESIZE];
+
+               rc = getpop(s, linebuf, sizeof(linebuf));
+
+               if (rc <= 0)
+                       break;  /* Error or EOF */
+
+               if (strcasecmp(linebuf, "CAPA") == 0) {
+                       putpopbulk(s, "+OK We have no capabilities, really\r\n"
+                                  "FAKE-CAPABILITY\r\n.\r\n");
+               } else if (strncasecmp(linebuf, "USER ", 5) == 0) {
+                       if (strcmp(linebuf + 5, argv[3]) == 0) {
+                               putpop(s, "+OK Niiiice!");
+                               user = 1;
+                       } else {
+                               putpop(s, "-ERR Don't play me, bro!");
+                       }
+               } else if (strncasecmp(linebuf, "PASS ", 5) == 0) {
+                       CHECKUSER();
+                       if (strcmp(linebuf + 5, argv[4]) == 0) {
+                               putpop(s, "+OK Aren't you a sight "
+                                      "for sore eyes!");
+                               pass = 1;
+                       } else {
+                               putpop(s, "-ERR C'mon!");
+                       }
+               } else if (strcasecmp(linebuf, "STAT") == 0) {
+                       CHECKUSERPASS();
+                       if (deleted) {
+                               strncpy(linebuf, "+OK 0 0", sizeof(linebuf));
+                       } else {
+                               snprintf(linebuf, sizeof(linebuf),
+                                        "+OK 1 %d", octets);
+                       }
+                       putpop(s, linebuf);
+               } else if (strcasecmp(linebuf, "RETR 1") == 0) {
+                       CHECKUSERPASS();
+                       if (deleted) {
+                               putpop(s, "-ERR Sorry, don't have it anymore");
+                       } else {
+                               char *buf = readmessage(f);
+                               putpop(s, "+OK Here you go ...");
+                               putpopbulk(s, buf);
+                               free(buf);
+                       }
+               } else if (strncasecmp(linebuf, "RETR ", 5) == 0) {
+                       CHECKUSERPASS();
+                       putpop(s, "-ERR Sorry man, out of range!");
+               } else if (strcasecmp(linebuf, "DELE 1") == 0) {
+                       CHECKUSERPASS();
+                       if (deleted) {
+                               putpop(s, "-ERR Um, didn't you tell me "
+                                      "to delete it already?");
+                       } else {
+                               putpop(s, "+OK Alright man, I got rid of it");
+                               deleted = 1;
+                       }
+               } else if (strncasecmp(linebuf, "DELE ", 5) == 0) {
+                       CHECKUSERPASS();
+                       putpop(s, "-ERR Sorry man, out of range!");
+               } else if (strcasecmp(linebuf, "QUIT") == 0) {
+                       putpop(s, "+OK See ya, wouldn't want to be ya!");
+                       close(s);
+                       break;
+               } else {
+                       putpop(s, "-ERR Um, what?");
+               }
+       }
+
+       exit(0);
+}
+
+/*
+ * Send one line to the POP client
+ */
+
+static void
+putpop(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);
+}
+
+/*
+ * Put one big buffer to the POP server.  Should have already had the line
+ * endings set up and dot-stuffed if necessary.
+ */
+
+static void
+putpopbulk(int socket, char *data)
+{
+       ssize_t datalen = strlen(data);
+
+       write(socket, data, datalen);
+}
+
+/*
+ * Get one line from the POP server.  We don't do any buffering here.
+ */
+
+static int
+getpop(int socket, char *data, ssize_t len)
+{
+       int cc;
+       int offset = 0;
+
+       for (;;) {
+               cc = read(socket, data + offset, len - offset);
+
+               if (cc < 0) {
+                       fprintf(stderr, "Read failed: %s\n", strerror(errno));
+                       exit(1);
+               }
+
+               if (cc == 0) {
+                       return 0;
+               }
+
+               offset += cc;
+
+               if (offset >= len) {
+                       fprintf(stderr, "Input buffer overflow "
+                               "(%d bytes)\n", (int) len);
+                       exit(1);
+               }
+
+               if (data[offset - 1] == '\n' && data[offset - 2] == '\r') {
+                       data[offset - 2] = '\0';
+                       return offset - 2;
+               }
+       }
+}
+
+#define HAVEROOM(buf, size, used, new) do { \
+               if (used + new > size - 1) { \
+                       buf = realloc(buf, size += BUFALLOC); \
+               } \
+       } while (0)
+       
+/*
+ * Read a file and return it as one malloc()'d buffer.  Convert \n to \r\n
+ * and dot-stuff if necessary.
+ */
+
+static char *
+readmessage(FILE *file)
+{
+       char *buffer = malloc(BUFALLOC);
+       ssize_t bufsize = BUFALLOC, used = 0;
+       char linebuf[LINESIZE];
+       int i;
+
+       buffer[0] = '\0';
+
+       while (fgets(linebuf, sizeof(linebuf), file)) {
+               if (strcmp(linebuf, ".\n") == 0) {
+                       HAVEROOM(buffer, bufsize, used, 4);
+                       strcat(buffer, "..\r\n");
+               } else {
+                       i = strlen(linebuf);
+                       if (i && linebuf[i - 1] == '\n') {
+                               HAVEROOM(buffer, bufsize, used, i + 1);
+                               linebuf[i - 1] = '\0';
+                               strcat(buffer, linebuf);
+                               strcat(buffer, "\r\n");
+                       } else {
+                               HAVEROOM(buffer, bufsize, used, i);
+                               strcat(buffer, linebuf);
+                       }
+               }
+       }
+
+       /*
+        * Put a terminating dot at the end
+        */
+
+       HAVEROOM(buffer, bufsize, used, 3);
+
+       strcat(buffer, ".\r\n");
+
+       return buffer;
+}
diff --git a/test/inc/test-pop b/test/inc/test-pop
new file mode 100755 (executable)
index 0000000..a8bb87f
--- /dev/null
@@ -0,0 +1,62 @@
+#!/bin/sh
+######################################################
+#
+# Test POP support in inc
+# Only tests checking of local maildrop, does not
+# test checking of POP server.
+#
+######################################################
+
+set -e
+
+if test -z "${MH_OBJ_DIR}"; then
+    srcdir=`dirname $0`/../..
+    MH_OBJ_DIR=`cd $srcdir && pwd`; export MH_OBJ_DIR
+fi
+
+. "$MH_OBJ_DIR/test/common.sh"
+
+setup_test
+
+#
+# Some extra stuff we need for POP support
+#
+
+TESTUSER=testuser
+TESTPASS=testuserpass
+testport=65413
+
+HOME="${MH_TEST_DIR}"; export HOME
+echo "default login ${TESTUSER} password ${TESTPASS}" > ${HOME}/.netrc
+chmod 600 ${HOME}/.netrc
+
+expected=$MH_TEST_DIR/$$.expected
+expected_err=$MH_TEST_DIR/$$.expected_err
+actual=$MH_TEST_DIR/$$.actual
+actual_err=$MH_TEST_DIR/$$.actual_err
+testmessage=$MH_TEST_DIR/testmessage
+
+cat > $testmessage <<EOM
+Received: From somewhere
+From: No Such User <nosuch@example.com>
+To: Some Other User <someother@example.com>
+Subject: Hello
+Date: Sun, 17 Dec 2006 12:13:14 -0500
+
+Hey man, how's it going?
+.
+Hope you're doing better.
+EOM
+
+pid=`"${MH_OBJ_DIR}/test/fakepop" "$testmessage" "$testport" \
+                       "$TESTUSER" "$TESTPASS"`
+
+run_test "inc -user ${TESTUSER} -host 127.0.0.1 -port $testport -width 80" \
+       "Incorporating new mail into inbox...
+
+  11+ 12/17 No Such User       Hello<<Hey man, how's it going? . Hope you're do"
+
+check `mhpath +inbox 11` $testmessage
+rm -f $testmessage
+
+exit ${failed:-0}