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