]> diplodocus.org Git - nmh/blob - test/fakepop.c
Convert all argument processing over to using X-Macros for cleanup.
[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 <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 }