]> diplodocus.org Git - nmh/blob - test/fakepop.c
show: remove unreachable code
[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 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 }