]> diplodocus.org Git - nmh/blob - test/server.c
Fix tests with oauth disabled.
[nmh] / test / server.c
1 /*
2 * server.c - Utilities for fake servers used by the nmh test suite
3 *
4 * This code is Copyright (c) 2014, 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 static const char *PIDFN = NULL;
24
25 static void killpidfile(void);
26 static void handleterm(int);
27
28 static int
29 try_bind(int socket, const struct sockaddr *address, socklen_t len)
30 {
31 int i, status;
32 for (i = 0; i < 5; i++) {
33 if ((status = bind(socket, address, len)) == 0) {
34 return 0;
35 }
36 sleep(1);
37 }
38
39 return status;
40 }
41
42 int
43 serve(const char *pidfn, const char *port)
44 {
45 struct addrinfo hints, *res;
46 int rc, l, conn, on;
47 FILE *pid;
48 pid_t child;
49 fd_set readfd;
50 struct stat st;
51 struct timeval tv;
52
53 PIDFN = pidfn;
54
55 /*
56 * If there is a pid file already around, kill the previously running
57 * fakesmtp process. Hopefully this will reduce the race conditions
58 * that crop up when running the test suite.
59 */
60
61 if (stat(pidfn, &st) == 0) {
62 long oldpid;
63
64 if (!(pid = fopen(pidfn, "r"))) {
65 fprintf(stderr, "Cannot open %s (%s), continuing ...\n",
66 pidfn, strerror(errno));
67 } else {
68 rc = fscanf(pid, "%ld", &oldpid);
69 fclose(pid);
70
71 if (rc != 1) {
72 fprintf(stderr, "Unable to parse pid in %s,"
73 " continuing ...\n",
74 pidfn);
75 } else {
76 kill((pid_t) oldpid, SIGTERM);
77 }
78 }
79
80 unlink(pidfn);
81 }
82
83 memset(&hints, 0, sizeof(hints));
84
85 hints.ai_family = PF_INET;
86 hints.ai_socktype = SOCK_STREAM;
87 hints.ai_protocol = IPPROTO_TCP;
88 hints.ai_flags = AI_PASSIVE;
89
90 rc = getaddrinfo("127.0.0.1", port, &hints, &res);
91
92 if (rc) {
93 fprintf(stderr, "Unable to resolve localhost/%s: %s\n",
94 port, gai_strerror(rc));
95 exit(1);
96 }
97
98 l = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
99
100 if (l == -1) {
101 fprintf(stderr, "Unable to create listening socket: %s\n",
102 strerror(errno));
103 exit(1);
104 }
105
106 on = 1;
107
108 if (setsockopt(l, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
109 fprintf(stderr, "Unable to set SO_REUSEADDR: %s\n",
110 strerror(errno));
111 exit(1);
112 }
113
114 if (try_bind(l, res->ai_addr, res->ai_addrlen) == -1) {
115 fprintf(stderr, "Unable to bind socket: %s\n", strerror(errno));
116 exit(1);
117 }
118
119 freeaddrinfo(res);
120
121 if (listen(l, 1) == -1) {
122 fprintf(stderr, "Unable to listen on socket: %s\n",
123 strerror(errno));
124 exit(1);
125 }
126
127 /*
128 * Now we fork() and print out the process ID of our child
129 * for scripts to use. Once we do that, then exit.
130 */
131
132 child = fork();
133
134 switch (child) {
135 case -1:
136 fprintf(stderr, "Unable to fork child: %s\n", strerror(errno));
137 exit(1);
138 break;
139 case 0:
140 /*
141 * Close stdin & stdout, otherwise people can
142 * think we're still doing stuff. For now leave stderr
143 * open.
144 */
145 fclose(stdin);
146 fclose(stdout);
147 break;
148 default:
149 /* XXX why? it's never used... */
150 printf("%ld\n", (long) child);
151 exit(0);
152 }
153
154 /*
155 * Now that our socket & files are set up, wait 30 seconds for
156 * a connection. If there isn't one, then exit.
157 */
158
159 if (!(pid = fopen(pidfn, "w"))) {
160 fprintf(stderr, "Cannot open %s: %s\n",
161 pidfn, strerror(errno));
162 exit(1);
163 }
164
165 fprintf(pid, "%ld\n", (long) getpid());
166 fclose(pid);
167
168 signal(SIGTERM, handleterm);
169 atexit(killpidfile);
170
171 FD_ZERO(&readfd);
172 FD_SET(l, &readfd);
173 tv.tv_sec = 30;
174 tv.tv_usec = 0;
175
176 rc = select(l + 1, &readfd, NULL, NULL, &tv);
177
178 if (rc < 0) {
179 fprintf(stderr, "select() failed: %s\n", strerror(errno));
180 exit(1);
181 }
182
183 /*
184 * I think if we get a timeout, we should just exit quietly.
185 */
186
187 if (rc == 0) {
188 exit(1);
189 }
190
191 /*
192 * Alright, got a connection! Accept it.
193 */
194
195 if ((conn = accept(l, NULL, NULL)) == -1) {
196 fprintf(stderr, "Unable to accept connection: %s\n",
197 strerror(errno));
198 exit(1);
199 }
200
201 close(l);
202
203 return conn;
204 }
205
206 /*
207 * Write a line (adding \r\n) to the client on the other end
208 */
209 void
210 putcrlf(int socket, char *data)
211 {
212 struct iovec iov[2];
213
214 iov[0].iov_base = data;
215 iov[0].iov_len = strlen(data);
216 iov[1].iov_base = "\r\n";
217 iov[1].iov_len = 2;
218
219 /* ECONNRESET just means the client already closed its end */
220 /* XXX is it useful to log errors here at all? */
221 if (writev(socket, iov, 2) < 0 && errno != ECONNRESET) {
222 perror ("writev");
223 }
224 }
225
226 /*
227 * Handle a SIGTERM
228 */
229
230 static void
231 handleterm(int signal)
232 {
233 (void) signal;
234
235 killpidfile();
236 fflush(NULL);
237 _exit(1);
238 }
239
240 /*
241 * Get rid of our pid file
242 */
243
244 static void
245 killpidfile(void)
246 {
247 if (PIDFN != NULL) {
248 unlink(PIDFN);
249 }
250 }