2 * fakepop - A fake POP server used by the nmh test suite
4 * This code is Copyright (c) 2012, by the authors of nmh. See the
5 * COPYRIGHT file in the root directory of the nmh distribution for
6 * complete copyright information.
15 #include <sys/socket.h>
16 #include <sys/types.h>
17 #include <sys/select.h>
23 #define PIDFILE "/tmp/fakepop.pid"
27 #define CHECKUSER() if (!user) { \
28 putpop(s, "-ERR Aren't you forgetting " \
29 "something? Like the USER command?"); \
32 #define CHECKUSERPASS() CHECKUSER() \
34 putpop(s, "-ERR Um, hello? Forget to " \
39 static void putpop(int, char *);
40 static void putpopbulk(int, char *);
41 static int getpop(int, char *, ssize_t);
42 static char *readmessage(FILE *);
45 main(int argc, char *argv[])
47 struct addrinfo hints, *res;
54 int octets = 0, rc, l, s, on, user = 0, pass = 0, deleted = 0;
57 fprintf(stderr, "Usage: %s mail-file port username "
58 "password\n", argv[0]);
62 if (!(f = fopen(argv[1], "r"))) {
63 fprintf(stderr, "Unable to open message file \"%s\": %s\n",
64 argv[1], strerror(errno));
69 * POP wants the size of the maildrop in bytes, but with \r\n line
70 * endings. Calculate that.
73 while (fgets(line, sizeof(line), f)) {
74 octets += strlen(line);
75 if (strrchr(line, '\n'))
82 * If there is a pid file around, kill the previously running
86 if (stat(PIDFILE, &st) == 0) {
89 if (!(pid = fopen(PIDFILE, "r"))) {
90 fprintf(stderr, "Cannot open " PIDFILE
91 " (%s), continuing ...\n", strerror(errno));
93 rc = fscanf(pid, "%ld", &oldpid);
97 fprintf(stderr, "Unable to parse pid in "
98 PIDFILE ", continuing ...\n");
100 kill((pid_t) oldpid, SIGTERM);
107 memset(&hints, 0, sizeof(hints));
109 hints.ai_family = PF_INET;
110 hints.ai_socktype = SOCK_STREAM;
111 hints.ai_protocol = IPPROTO_TCP;
112 hints.ai_flags = AI_PASSIVE;
114 rc = getaddrinfo("127.0.0.1", argv[2], &hints, &res);
117 fprintf(stderr, "Unable to resolve localhost/%s: %s\n",
118 argv[2], gai_strerror(rc));
122 l = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
125 fprintf(stderr, "Unable to create listening socket: %s\n",
132 if (setsockopt(l, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
133 fprintf(stderr, "Unable to set SO_REUSEADDR: %s\n",
138 if (bind(l, res->ai_addr, res->ai_addrlen) == -1) {
139 fprintf(stderr, "Unable to bind socket: %s\n", strerror(errno));
143 if (listen(l, 1) == -1) {
144 fprintf(stderr, "Unable to listen on socket: %s\n",
150 * Fork off a copy of ourselves, print out our child pid, then
154 switch (child = fork()) {
156 fprintf(stderr, "Unable to fork child: %s\n", strerror(errno));
161 * Close stdin and stdout so $() in the shell will get an
162 * EOF. For now leave stderr open.
168 printf("%ld\n", (long) child);
173 * Now that our socket and files are set up, wait 30 seconds for
174 * a connection. If there isn't one, then exit.
177 if (!(pid = fopen(PIDFILE, "w"))) {
178 fprintf(stderr, "Cannot open " PIDFILE ": %s\n",
183 fprintf(pid, "%ld\n", (long) getpid());
192 rc = select(l + 1, &readfd, NULL, NULL, &tv);
195 fprintf(stderr, "select() failed: %s\n", strerror(errno));
200 * If we get a timeout, just silently exit
208 * We got a connection; accept it. Right after that close our
209 * listening socket so we won't get any more connections on it.
212 if ((s = accept(l, NULL, NULL)) == -1) {
213 fprintf(stderr, "Unable to accept connection: %s\n",
221 * Pretend to be a POP server
224 putpop(s, "+OK Not really a POP server, but we play one on TV");
227 char linebuf[LINESIZE];
229 rc = getpop(s, linebuf, sizeof(linebuf));
232 break; /* Error or EOF */
234 if (strcasecmp(linebuf, "CAPA") == 0) {
235 putpopbulk(s, "+OK We have no capabilities, really\r\n"
236 "FAKE-CAPABILITY\r\n.\r\n");
237 } else if (strncasecmp(linebuf, "USER ", 5) == 0) {
238 if (strcmp(linebuf + 5, argv[3]) == 0) {
239 putpop(s, "+OK Niiiice!");
242 putpop(s, "-ERR Don't play me, bro!");
244 } else if (strncasecmp(linebuf, "PASS ", 5) == 0) {
246 if (strcmp(linebuf + 5, argv[4]) == 0) {
247 putpop(s, "+OK Aren't you a sight "
251 putpop(s, "-ERR C'mon!");
253 } else if (strcasecmp(linebuf, "STAT") == 0) {
256 strncpy(linebuf, "+OK 0 0", sizeof(linebuf));
258 snprintf(linebuf, sizeof(linebuf),
262 } else if (strcasecmp(linebuf, "RETR 1") == 0) {
265 putpop(s, "-ERR Sorry, don't have it anymore");
267 char *buf = readmessage(f);
268 putpop(s, "+OK Here you go ...");
272 } else if (strncasecmp(linebuf, "RETR ", 5) == 0) {
274 putpop(s, "-ERR Sorry man, out of range!");
275 } else if (strcasecmp(linebuf, "DELE 1") == 0) {
278 putpop(s, "-ERR Um, didn't you tell me "
279 "to delete it already?");
281 putpop(s, "+OK Alright man, I got rid of it");
284 } else if (strncasecmp(linebuf, "DELE ", 5) == 0) {
286 putpop(s, "-ERR Sorry man, out of range!");
287 } else if (strcasecmp(linebuf, "QUIT") == 0) {
288 putpop(s, "+OK See ya, wouldn't want to be ya!");
292 putpop(s, "-ERR Um, what?");
300 * Send one line to the POP client
304 putpop(int socket, char *data)
308 iov[0].iov_base = data;
309 iov[0].iov_len = strlen(data);
310 iov[1].iov_base = "\r\n";
313 writev(socket, iov, 2);
317 * Put one big buffer to the POP server. Should have already had the line
318 * endings set up and dot-stuffed if necessary.
322 putpopbulk(int socket, char *data)
324 ssize_t datalen = strlen(data);
326 write(socket, data, datalen);
330 * Get one line from the POP server. We don't do any buffering here.
334 getpop(int socket, char *data, ssize_t len)
340 cc = read(socket, data + offset, len - offset);
343 fprintf(stderr, "Read failed: %s\n", strerror(errno));
354 fprintf(stderr, "Input buffer overflow "
355 "(%d bytes)\n", (int) len);
359 if (data[offset - 1] == '\n' && data[offset - 2] == '\r') {
360 data[offset - 2] = '\0';
366 #define HAVEROOM(buf, size, used, new) do { \
367 if (used + new > size - 1) { \
368 buf = realloc(buf, size += BUFALLOC); \
373 * Read a file and return it as one malloc()'d buffer. Convert \n to \r\n
374 * and dot-stuff if necessary.
378 readmessage(FILE *file)
380 char *buffer = malloc(BUFALLOC);
381 ssize_t bufsize = BUFALLOC, used = 0;
382 char linebuf[LINESIZE];
387 while (fgets(linebuf, sizeof(linebuf), file)) {
388 if (strcmp(linebuf, ".\n") == 0) {
389 HAVEROOM(buffer, bufsize, used, 4);
390 strcat(buffer, "..\r\n");
393 if (i && linebuf[i - 1] == '\n') {
394 HAVEROOM(buffer, bufsize, used, i + 1);
395 linebuf[i - 1] = '\0';
396 strcat(buffer, linebuf);
397 strcat(buffer, "\r\n");
399 HAVEROOM(buffer, bufsize, used, i);
400 strcat(buffer, linebuf);
406 * Put a terminating dot at the end
409 HAVEROOM(buffer, bufsize, used, 3);
411 strcat(buffer, ".\r\n");