From d6b1aed6b65b10cd5db73a5b2080c4bdefefff39 Mon Sep 17 00:00:00 2001 From: Ken Hornstein Date: Wed, 14 Mar 2012 15:51:22 -0400 Subject: [PATCH] A new test program, fakesmtp, to help us test post. --- .gitignore | 1 + Makefile.am | 5 +- test/fakesmtp.c | 310 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 test/fakesmtp.c diff --git a/.gitignore b/.gitignore index d1dd750..9031616 100644 --- a/.gitignore +++ b/.gitignore @@ -96,6 +96,7 @@ a.out.DSYM/ /uip/whatnow /uip/whom /uip/*.exe +/test/fakesmtp /test/getfullname /test/getfqdn diff --git a/Makefile.am b/Makefile.am index 9ca7740..e402ca1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -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 index 0000000..19df2e9 --- /dev/null +++ b/test/fakesmtp.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); +} -- 1.7.10.4