Copied atexit() code from fakesmtp.c to fakepop.c so that its
[mmh] / test / fakepop.c
1 /*
2  * fakepop - A fake POP server used by the nmh test suite
3  *
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.
7  */
8
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <unistd.h>
13 #include <netdb.h>
14 #include <errno.h>
15 #include <sys/socket.h>
16 #include <sys/types.h>
17 #include <sys/select.h>
18 #include <sys/stat.h>
19 #include <sys/uio.h>
20 #include <limits.h>
21 #include <signal.h>
22
23 #define PIDFILE "/tmp/fakepop.pid"
24 #define LINESIZE 1024
25 #define BUFALLOC 4096
26
27 #define CHECKUSER()     if (!user) { \
28                                 putpop(s, "-ERR Aren't you forgetting " \
29                                        "something?  Like the USER command?"); \
30                                 continue; \
31                         }
32 #define CHECKUSERPASS() CHECKUSER() \
33                         if (! pass) { \
34                                 putpop(s, "-ERR Um, hello?  Forget to " \
35                                        "log in?"); \
36                                 continue; \
37                         }
38
39 static void killpidfile(void);
40 static void handleterm(int);
41 static void putpop(int, char *);
42 static void putpopbulk(int, char *);
43 static int getpop(int, char *, ssize_t);
44 static char *readmessage(FILE *);
45
46 int
47 main(int argc, char *argv[])
48 {
49         struct addrinfo hints, *res;
50         struct stat st;
51         FILE *f, *pid;
52         char line[LINESIZE];
53         fd_set readfd;
54         struct timeval tv;
55         pid_t child;
56         int octets = 0, rc, l, s, on, user = 0, pass = 0, deleted = 0;
57
58         if (argc != 5) {
59                 fprintf(stderr, "Usage: %s mail-file port username "
60                         "password\n", argv[0]);
61                 exit(1);
62         }
63
64         if (!(f = fopen(argv[1], "r"))) {
65                 fprintf(stderr, "Unable to open message file \"%s\": %s\n",
66                         argv[1], strerror(errno));
67                 exit(1);
68         }
69
70         /*
71          * POP wants the size of the maildrop in bytes, but with \r\n line
72          * endings.  Calculate that.
73          */
74
75         while (fgets(line, sizeof(line), f)) {
76                 octets += strlen(line);
77                 if (strrchr(line, '\n'))
78                         octets++;
79         }
80
81         rewind(f);
82
83         /*
84          * If there is a pid file around, kill the previously running
85          * fakepop process.
86          */
87
88         if (stat(PIDFILE, &st) == 0) {
89                 long oldpid;
90
91                 if (!(pid = fopen(PIDFILE, "r"))) {
92                         fprintf(stderr, "Cannot open " PIDFILE
93                                 " (%s), continuing ...\n", strerror(errno));
94                 } else {
95                         rc = fscanf(pid, "%ld", &oldpid);
96                         fclose(pid);
97
98                         if (rc != 1) {
99                                 fprintf(stderr, "Unable to parse pid in "
100                                         PIDFILE ", continuing ...\n");
101                         } else {
102                                 kill((pid_t) oldpid, SIGTERM);
103                         }
104                 }
105
106                 unlink(PIDFILE);
107         }
108
109         memset(&hints, 0, sizeof(hints));
110
111         hints.ai_family = PF_INET;
112         hints.ai_socktype = SOCK_STREAM;
113         hints.ai_protocol = IPPROTO_TCP;
114         hints.ai_flags = AI_PASSIVE;
115
116         rc = getaddrinfo("127.0.0.1", argv[2], &hints, &res);
117
118         if (rc) {
119                 fprintf(stderr, "Unable to resolve localhost/%s: %s\n",
120                         argv[2], gai_strerror(rc));
121                 exit(1);
122         }
123
124         l = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
125
126         if (l == -1) {
127                 fprintf(stderr, "Unable to create listening socket: %s\n",
128                         strerror(errno));
129                 exit(1);
130         }
131
132         on = 1;
133
134         if (setsockopt(l, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
135                 fprintf(stderr, "Unable to set SO_REUSEADDR: %s\n",
136                         strerror(errno));
137                 exit(1);
138         }
139
140         if (bind(l, res->ai_addr, res->ai_addrlen) == -1) {
141                 fprintf(stderr, "Unable to bind socket: %s\n", strerror(errno));
142                 exit(1);
143         }
144
145         if (listen(l, 1) == -1) {
146                 fprintf(stderr, "Unable to listen on socket: %s\n",
147                         strerror(errno));
148                 exit(1);
149         }
150
151         /*
152          * Fork off a copy of ourselves, print out our child pid, then
153          * exit.
154          */
155
156         switch (child = fork()) {
157         case -1:
158                 fprintf(stderr, "Unable to fork child: %s\n", strerror(errno));
159                 exit(1);
160                 break;
161         case 0:
162                 /*
163                  * Close stdin and stdout so $() in the shell will get an
164                  * EOF.  For now leave stderr open.
165                  */
166                 fclose(stdin);
167                 fclose(stdout);
168                 break;
169         default:
170                 printf("%ld\n", (long) child);
171                 exit(0);
172         }
173
174         /*
175          * Now that our socket and files are set up, wait 30 seconds for
176          * a connection.  If there isn't one, then exit.
177          */
178
179         if (!(pid = fopen(PIDFILE, "w"))) {
180                 fprintf(stderr, "Cannot open " PIDFILE ": %s\n",
181                         strerror(errno));
182                 exit(1);
183         }
184
185         fprintf(pid, "%ld\n", (long) getpid());
186         fclose(pid);
187
188         signal(SIGTERM, handleterm);
189         atexit(killpidfile);
190
191         FD_ZERO(&readfd);
192         FD_SET(l, &readfd);
193
194         tv.tv_sec = 30;
195         tv.tv_usec = 0;
196
197         rc = select(l + 1, &readfd, NULL, NULL, &tv);
198
199         if (rc < 0) {
200                 fprintf(stderr, "select() failed: %s\n", strerror(errno));
201                 exit(1);
202         }
203
204         /*
205          * If we get a timeout, just silently exit
206          */
207
208         if (rc == 0) {
209                 exit(1);
210         }
211
212         /*
213          * We got a connection; accept it.  Right after that close our
214          * listening socket so we won't get any more connections on it.
215          */
216
217         if ((s = accept(l, NULL, NULL)) == -1) {
218                 fprintf(stderr, "Unable to accept connection: %s\n",
219                         strerror(errno));
220                 exit(1);
221         }
222
223         close(l);
224
225         /*
226          * Pretend to be a POP server
227          */
228
229         putpop(s, "+OK Not really a POP server, but we play one on TV");
230
231         for (;;) {
232                 char linebuf[LINESIZE];
233
234                 rc = getpop(s, linebuf, sizeof(linebuf));
235
236                 if (rc <= 0)
237                         break;  /* Error or EOF */
238
239                 if (strcasecmp(linebuf, "CAPA") == 0) {
240                         putpopbulk(s, "+OK We have no capabilities, really\r\n"
241                                    "FAKE-CAPABILITY\r\n.\r\n");
242                 } else if (strncasecmp(linebuf, "USER ", 5) == 0) {
243                         if (strcmp(linebuf + 5, argv[3]) == 0) {
244                                 putpop(s, "+OK Niiiice!");
245                                 user = 1;
246                         } else {
247                                 putpop(s, "-ERR Don't play me, bro!");
248                         }
249                 } else if (strncasecmp(linebuf, "PASS ", 5) == 0) {
250                         CHECKUSER();
251                         if (strcmp(linebuf + 5, argv[4]) == 0) {
252                                 putpop(s, "+OK Aren't you a sight "
253                                        "for sore eyes!");
254                                 pass = 1;
255                         } else {
256                                 putpop(s, "-ERR C'mon!");
257                         }
258                 } else if (strcasecmp(linebuf, "STAT") == 0) {
259                         CHECKUSERPASS();
260                         if (deleted) {
261                                 strncpy(linebuf, "+OK 0 0", sizeof(linebuf));
262                         } else {
263                                 snprintf(linebuf, sizeof(linebuf),
264                                          "+OK 1 %d", octets);
265                         }
266                         putpop(s, linebuf);
267                 } else if (strcasecmp(linebuf, "RETR 1") == 0) {
268                         CHECKUSERPASS();
269                         if (deleted) {
270                                 putpop(s, "-ERR Sorry, don't have it anymore");
271                         } else {
272                                 char *buf = readmessage(f);
273                                 putpop(s, "+OK Here you go ...");
274                                 putpopbulk(s, buf);
275                                 free(buf);
276                         }
277                 } else if (strncasecmp(linebuf, "RETR ", 5) == 0) {
278                         CHECKUSERPASS();
279                         putpop(s, "-ERR Sorry man, out of range!");
280                 } else if (strcasecmp(linebuf, "DELE 1") == 0) {
281                         CHECKUSERPASS();
282                         if (deleted) {
283                                 putpop(s, "-ERR Um, didn't you tell me "
284                                        "to delete it already?");
285                         } else {
286                                 putpop(s, "+OK Alright man, I got rid of it");
287                                 deleted = 1;
288                         }
289                 } else if (strncasecmp(linebuf, "DELE ", 5) == 0) {
290                         CHECKUSERPASS();
291                         putpop(s, "-ERR Sorry man, out of range!");
292                 } else if (strcasecmp(linebuf, "QUIT") == 0) {
293                         putpop(s, "+OK See ya, wouldn't want to be ya!");
294                         close(s);
295                         break;
296                 } else {
297                         putpop(s, "-ERR Um, what?");
298                 }
299         }
300
301         exit(0);
302 }
303
304 /*
305  * Send one line to the POP client
306  */
307
308 static void
309 putpop(int socket, char *data)
310 {
311         struct iovec iov[2];
312
313         iov[0].iov_base = data;
314         iov[0].iov_len = strlen(data);
315         iov[1].iov_base = "\r\n";
316         iov[1].iov_len = 2;
317
318         writev(socket, iov, 2);
319 }
320
321 /*
322  * Put one big buffer to the POP server.  Should have already had the line
323  * endings set up and dot-stuffed if necessary.
324  */
325
326 static void
327 putpopbulk(int socket, char *data)
328 {
329         ssize_t datalen = strlen(data);
330
331         write(socket, data, datalen);
332 }
333
334 /*
335  * Get one line from the POP server.  We don't do any buffering here.
336  */
337
338 static int
339 getpop(int socket, char *data, ssize_t len)
340 {
341         int cc;
342         int offset = 0;
343
344         for (;;) {
345                 cc = read(socket, data + offset, len - offset);
346
347                 if (cc < 0) {
348                         fprintf(stderr, "Read failed: %s\n", strerror(errno));
349                         exit(1);
350                 }
351
352                 if (cc == 0) {
353                         return 0;
354                 }
355
356                 offset += cc;
357
358                 if (offset >= len) {
359                         fprintf(stderr, "Input buffer overflow "
360                                 "(%d bytes)\n", (int) len);
361                         exit(1);
362                 }
363
364                 if (data[offset - 1] == '\n' && data[offset - 2] == '\r') {
365                         data[offset - 2] = '\0';
366                         return offset - 2;
367                 }
368         }
369 }
370
371 #define HAVEROOM(buf, size, used, new) do { \
372                 if (used + new > size - 1) { \
373                         buf = realloc(buf, size += BUFALLOC); \
374                 } \
375         } while (0)
376         
377 /*
378  * Read a file and return it as one malloc()'d buffer.  Convert \n to \r\n
379  * and dot-stuff if necessary.
380  */
381
382 static char *
383 readmessage(FILE *file)
384 {
385         char *buffer = malloc(BUFALLOC);
386         ssize_t bufsize = BUFALLOC, used = 0;
387         char linebuf[LINESIZE];
388         int i;
389
390         buffer[0] = '\0';
391
392         while (fgets(linebuf, sizeof(linebuf), file)) {
393                 if (strcmp(linebuf, ".\n") == 0) {
394                         HAVEROOM(buffer, bufsize, used, 4);
395                         strcat(buffer, "..\r\n");
396                 } else {
397                         i = strlen(linebuf);
398                         if (i && linebuf[i - 1] == '\n') {
399                                 HAVEROOM(buffer, bufsize, used, i + 1);
400                                 linebuf[i - 1] = '\0';
401                                 strcat(buffer, linebuf);
402                                 strcat(buffer, "\r\n");
403                         } else {
404                                 HAVEROOM(buffer, bufsize, used, i);
405                                 strcat(buffer, linebuf);
406                         }
407                 }
408         }
409
410         /*
411          * Put a terminating dot at the end
412          */
413
414         HAVEROOM(buffer, bufsize, used, 3);
415
416         strcat(buffer, ".\r\n");
417
418         return buffer;
419 }
420
421 /*
422  * Handle a SIGTERM
423  */
424
425 static void
426 handleterm(int signal)
427 {
428         (void) signal;
429
430         killpidfile();
431         fflush(NULL);
432         _exit(1);
433 }
434
435 /*
436  * Get rid of our pid file
437  */
438
439 static void
440 killpidfile(void)
441 {
442         unlink(PIDFILE);
443 }