]> diplodocus.org Git - nmh/blob - test/fakesmtp.c
A number of bug fixes:
[nmh] / test / fakesmtp.c
1 /*
2 * fakesmtp - A fake SMTP 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 <signal.h>
22
23 #define PIDFILE "/tmp/fakesmtp.pid"
24
25 #define LINESIZE 1024
26
27 static void killpidfile(void);
28 static void handleterm(int);
29 static void putsmtp(int, char *);
30 static int getsmtp(int, char *);
31
32 int
33 main(int argc, char *argv[])
34 {
35 struct addrinfo hints, *res;
36 int rc, l, conn, on, datamode;
37 FILE *f, *pid;
38 pid_t child;
39 fd_set readfd;
40 struct stat st;
41 struct timeval tv;
42
43 if (argc != 3) {
44 fprintf(stderr, "Usage: %s output-filename port\n", argv[0]);
45 exit(1);
46 }
47
48 if (!(f = fopen(argv[1], "w"))) {
49 fprintf(stderr, "Unable to open output file \"%s\": %s\n",
50 argv[1], strerror(errno));
51 exit(1);
52 }
53
54 /*
55 * If there is a pid file already around, kill the previously running
56 * fakesmtp process. Hopefully this will reduce the race conditions
57 * that crop up when running the test suite.
58 */
59
60 if (stat(PIDFILE, &st) == 0) {
61 long oldpid;
62
63 if (!(pid = fopen(PIDFILE, "r"))) {
64 fprintf(stderr, "Cannot open " PIDFILE
65 " (%s), continuing ...\n", strerror(errno));
66 } else {
67 rc = fscanf(pid, "%ld", &oldpid);
68 fclose(pid);
69
70 if (rc != 1) {
71 fprintf(stderr, "Unable to parse pid in "
72 PIDFILE ", continuing ...\n");
73 } else {
74 kill((pid_t) oldpid, SIGTERM);
75 }
76 }
77
78 unlink(PIDFILE);
79 }
80
81 memset(&hints, 0, sizeof(hints));
82
83 hints.ai_family = PF_INET;
84 hints.ai_socktype = SOCK_STREAM;
85 hints.ai_protocol = IPPROTO_TCP;
86 hints.ai_flags = AI_PASSIVE;
87
88 rc = getaddrinfo("127.0.0.1", argv[2], &hints, &res);
89
90 if (rc) {
91 fprintf(stderr, "Unable to resolve localhost/%s: %s\n",
92 argv[2], gai_strerror(rc));
93 exit(1);
94 }
95
96 l = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
97
98 if (l == -1) {
99 fprintf(stderr, "Unable to create listening socket: %s\n",
100 strerror(errno));
101 exit(1);
102 }
103
104 on = 1;
105
106 if (setsockopt(l, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
107 fprintf(stderr, "Unable to set SO_REUSEADDR: %s\n",
108 strerror(errno));
109 exit(1);
110 }
111
112 if (bind(l, res->ai_addr, res->ai_addrlen) == -1) {
113 fprintf(stderr, "Unable to bind socket: %s\n", strerror(errno));
114 exit(1);
115 }
116
117 if (listen(l, 1) == -1) {
118 fprintf(stderr, "Unable to listen on socket: %s\n",
119 strerror(errno));
120 exit(1);
121 }
122
123 /*
124 * Now we fork() and print out the process ID of our child
125 * for scripts to use. Once we do that, then exit.
126 */
127
128 child = fork();
129
130 switch (child) {
131 case -1:
132 fprintf(stderr, "Unable to fork child: %s\n", strerror(errno));
133 exit(1);
134 break;
135 case 0:
136 /*
137 * Close stdin & stdout, otherwise people can
138 * think we're still doing stuff. For now leave stderr
139 * open.
140 */
141 fclose(stdin);
142 fclose(stdout);
143 break;
144 default:
145 printf("%ld\n", (long) child);
146 exit(0);
147 }
148
149 /*
150 * Now that our socket & files are set up, wait 30 seconds for
151 * a connection. If there isn't one, then exit.
152 */
153
154 if (!(pid = fopen(PIDFILE, "w"))) {
155 fprintf(stderr, "Cannot open " PIDFILE ": %s\n",
156 strerror(errno));
157 exit(1);
158 }
159
160 fprintf(pid, "%ld\n", (long) getpid());
161 fclose(pid);
162
163 signal(SIGTERM, handleterm);
164 atexit(killpidfile);
165
166 FD_ZERO(&readfd);
167 FD_SET(l, &readfd);
168 tv.tv_sec = 30;
169 tv.tv_usec = 0;
170
171 rc = select(l + 1, &readfd, NULL, NULL, &tv);
172
173 if (rc < 0) {
174 fprintf(stderr, "select() failed: %s\n", strerror(errno));
175 exit(1);
176 }
177
178 /*
179 * I think if we get a timeout, we should just exit quietly.
180 */
181
182 if (rc == 0) {
183 exit(1);
184 }
185
186 /*
187 * Alright, got a connection! Accept it.
188 */
189
190 if ((conn = accept(l, NULL, NULL)) == -1) {
191 fprintf(stderr, "Unable to accept connection: %s\n",
192 strerror(errno));
193 exit(1);
194 }
195
196 close(l);
197
198 /*
199 * Pretend to be an SMTP server.
200 */
201
202 putsmtp(conn, "220 Not really an ESMTP server");
203 datamode = 0;
204
205 for (;;) {
206 char line[LINESIZE];
207
208 rc = getsmtp(conn, line);
209
210 if (rc == -1)
211 break; /* EOF */
212
213 fprintf(f, "%s\n", line);
214
215 /*
216 * If we're in DATA mode, then check to see if we've got
217 * a "."; otherwise, continue
218 */
219
220 if (datamode) {
221 if (strcmp(line, ".") == 0) {
222 datamode = 0;
223 putsmtp(conn, "250 Thanks for the info!");
224 }
225 continue;
226 }
227
228 /*
229 * Most commands we ignore and send the same response to.
230 */
231
232 if (strcmp(line, "QUIT") == 0) {
233 fclose(f);
234 f = NULL;
235 putsmtp(conn, "221 Later alligator!");
236 close(conn);
237 break;
238 } else if (strcmp(line, "DATA") == 0) {
239 putsmtp(conn, "354 Go ahead");
240 datamode = 1;
241 } else {
242 putsmtp(conn, "250 I'll buy that for a dollar!");
243 }
244 }
245
246 if (f)
247 fclose(f);
248
249 exit(0);
250 }
251
252 /*
253 * Write a line to the SMTP client on the other end
254 */
255
256 static void
257 putsmtp(int socket, char *data)
258 {
259 struct iovec iov[2];
260
261 iov[0].iov_base = data;
262 iov[0].iov_len = strlen(data);
263 iov[1].iov_base = "\r\n";
264 iov[1].iov_len = 2;
265
266 writev(socket, iov, 2);
267 }
268
269 /*
270 * Read a line (up to the \r\n)
271 */
272
273 static int
274 getsmtp(int socket, char *data)
275 {
276 int cc;
277 static unsigned int bytesinbuf = 0;
278 static char buffer[LINESIZE * 2], *p;
279
280 for (;;) {
281 /*
282 * Find our \r\n
283 */
284
285 if (bytesinbuf > 0 && (p = strchr(buffer, '\r')) &&
286 *(p + 1) == '\n') {
287 *p = '\0';
288 strncpy(data, buffer, LINESIZE);
289 data[LINESIZE - 1] = '\0';
290 cc = strlen(buffer);
291
292 /*
293 * Shuffle leftover bytes back to the beginning
294 */
295
296 bytesinbuf -= cc + 2; /* Don't forget \r\n */
297 if (bytesinbuf > 0) {
298 memmove(buffer, buffer + cc + 2, bytesinbuf);
299 }
300 return cc;
301 }
302
303 if (bytesinbuf >= sizeof(buffer)) {
304 fprintf(stderr, "Buffer overflow in getsmtp()!\n");
305 exit(1);
306 }
307
308 memset(buffer + bytesinbuf, 0, sizeof(buffer) - bytesinbuf);
309 cc = read(socket, buffer + bytesinbuf,
310 sizeof(buffer) - bytesinbuf);
311
312 if (cc < 0) {
313 fprintf(stderr, "Read failed: %s\n", strerror(errno));
314 exit(1);
315 }
316
317 if (cc == 0)
318 return -1;
319
320 bytesinbuf += cc;
321 }
322 }
323
324 /*
325 * Handle a SIGTERM
326 */
327
328 static void
329 handleterm(int signal)
330 {
331 (void) signal;
332
333 killpidfile();
334 fflush(NULL);
335 _exit(1);
336 }
337
338 /*
339 * Get rid of our pid file
340 */
341
342 static void
343 killpidfile(void)
344 {
345 unlink(PIDFILE);
346 }