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