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