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