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