#include <stdlib.h>
#include <string.h>
#include <unistd.h>
-#include <netdb.h>
#include <errno.h>
#include <sys/socket.h>
-#include <netinet/in.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 *);
+enum {
+ /* Processing top-level SMTP commands (e.g. EHLO, DATA). */
+ SMTP_TOP,
+
+ /* Processing payload of a DATA command. */
+ SMTP_DATA,
+
+ /* Looking for the blank line required by XOAUTH2 after 334 response. */
+ SMTP_XOAUTH_ERR
+};
+
+void putcrlf(int, char *);
+int serve(const char *, const 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;
- pid_t child;
- fd_set readfd;
- struct stat st;
- struct timeval tv;
+ int rc, conn, smtp_state;
+ FILE *f;
+ const char *xoauth = getenv("XOAUTH");
if (argc != 3) {
fprintf(stderr, "Usage: %s output-filename port\n", argv[0]);
exit(1);
}
- /*
- * If there is a pid file already around, kill the previously running
- * fakesmtp process. Hopefully this will reduce the race conditions
- * that crop up when running the test suite.
- */
-
- 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);
- }
-
- /*
- * Now we fork() and print out the process ID of our child
- * for scripts to use. Once we do that, then exit.
- */
-
- child = fork();
-
- switch (child) {
- case -1:
- fprintf(stderr, "Unable to fork child: %s\n", strerror(errno));
- exit(1);
- break;
- case 0:
- /*
- * Close stdin & stdout, otherwise people can
- * think we're still doing stuff. For now leave stderr
- * open.
- */
- fclose(stdin);
- fclose(stdout);
- break;
- default:
- printf("%ld\n", (long) child);
- exit(0);
- }
+ conn = serve(PIDFILE, argv[2]);
- /*
- * Now that our socket & 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);
-
- 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;
+ putcrlf(conn, "220 Not really an ESMTP server");
+ smtp_state = SMTP_TOP;
for (;;) {
char line[LINESIZE];
fprintf(f, "%s\n", line);
- /*
- * If we're in DATA mode, then check to see if we've got
- * a "."; otherwise, continue
- */
-
- if (datamode) {
+ switch (smtp_state) {
+ case SMTP_DATA:
if (strcmp(line, ".") == 0) {
- datamode = 0;
- putsmtp(conn, "250 Thanks for the info!");
+ smtp_state = SMTP_TOP;
+ putcrlf(conn, "250 Thanks for the info!");
}
continue;
+ case SMTP_XOAUTH_ERR:
+ smtp_state = SMTP_TOP;
+ putcrlf(conn, "535 Not no way, not no how!");
+ continue;
}
/*
if (strcmp(line, "QUIT") == 0) {
fclose(f);
f = NULL;
- putsmtp(conn, "221 Later alligator!");
+ putcrlf(conn, "221 Later alligator!");
close(conn);
break;
- } else if (strcmp(line, "DATA") == 0) {
- putsmtp(conn, "354 Go ahead");
- datamode = 1;
+ }
+ if (strcmp(line, "DATA") == 0) {
+ putcrlf(conn, "354 Go ahead");
+ smtp_state = SMTP_DATA;
+ continue;
+ }
+ if (xoauth == NULL) {
+ putcrlf(conn, "250-ready");
+ putcrlf(conn, "250-8BITMIME");
+ putcrlf(conn, "250-SMTPUTF8");
} else {
- putsmtp(conn, "250 I'll buy that for a dollar!");
+ /* XOAUTH2 support enabled; handle EHLO and AUTH. */
+ if (strncmp(line, "EHLO", 4) == 0) {
+ putcrlf(conn, "250-ready");
+ putcrlf(conn, "250 AUTH XOAUTH2");
+ continue;
+ }
+ if (strncmp(line, "AUTH", 4) == 0) {
+ if (strncmp(line, "AUTH XOAUTH2", 12) == 0
+ && strstr(line, xoauth) != NULL) {
+ putcrlf(conn, "235 OK come in");
+ continue;
+ }
+ putcrlf(conn, "334 base64-json-err");
+ smtp_state = SMTP_XOAUTH_ERR;
+ continue;
+ }
}
+ putcrlf(conn, "250 I'll buy that for a dollar!");
}
if (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)
*/
bytesinbuf += cc;
}
}
-
-/*
- * Handle a SIGTERM
- */
-
-static void
-handleterm(int signal)
-{
- (void) signal;
-
- killpidfile();
- fflush(NULL);
- _exit(1);
-}
-
-/*
- * Get rid of our pid file
- */
-
-static void
-killpidfile(void)
-{
- unlink(PIDFILE);
-}