]> diplodocus.org Git - nmh/blob - test/server.c
13f3c873f6c12bca8a739d4772850de7635182c4
[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 if (listen(l, 1) == -1) {
120 fprintf(stderr, "Unable to listen on socket: %s\n",
121 strerror(errno));
122 exit(1);
123 }
124
125 /*
126 * Now we fork() and print out the process ID of our child
127 * for scripts to use. Once we do that, then exit.
128 */
129
130 child = fork();
131
132 switch (child) {
133 case -1:
134 fprintf(stderr, "Unable to fork child: %s\n", strerror(errno));
135 exit(1);
136 break;
137 case 0:
138 /*
139 * Close stdin & stdout, otherwise people can
140 * think we're still doing stuff. For now leave stderr
141 * open.
142 */
143 fclose(stdin);
144 fclose(stdout);
145 break;
146 default:
147 /* XXX why? it's never used... */
148 printf("%ld\n", (long) child);
149 exit(0);
150 }
151
152 /*
153 * Now that our socket & files are set up, wait 30 seconds for
154 * a connection. If there isn't one, then exit.
155 */
156
157 if (!(pid = fopen(pidfn, "w"))) {
158 fprintf(stderr, "Cannot open %s: %s\n",
159 pidfn, strerror(errno));
160 exit(1);
161 }
162
163 fprintf(pid, "%ld\n", (long) getpid());
164 fclose(pid);
165
166 signal(SIGTERM, handleterm);
167 atexit(killpidfile);
168
169 FD_ZERO(&readfd);
170 FD_SET(l, &readfd);
171 tv.tv_sec = 30;
172 tv.tv_usec = 0;
173
174 rc = select(l + 1, &readfd, NULL, NULL, &tv);
175
176 if (rc < 0) {
177 fprintf(stderr, "select() failed: %s\n", strerror(errno));
178 exit(1);
179 }
180
181 /*
182 * I think if we get a timeout, we should just exit quietly.
183 */
184
185 if (rc == 0) {
186 exit(1);
187 }
188
189 /*
190 * Alright, got a connection! Accept it.
191 */
192
193 if ((conn = accept(l, NULL, NULL)) == -1) {
194 fprintf(stderr, "Unable to accept connection: %s\n",
195 strerror(errno));
196 exit(1);
197 }
198
199 close(l);
200
201 return conn;
202 }
203
204 /*
205 * Write a line (adding \r\n) to the client on the other end
206 */
207 void
208 putcrlf(int socket, char *data)
209 {
210 struct iovec iov[2];
211
212 iov[0].iov_base = data;
213 iov[0].iov_len = strlen(data);
214 iov[1].iov_base = "\r\n";
215 iov[1].iov_len = 2;
216
217 /* ECONNRESET just means the client already closed its end */
218 /* XXX is it useful to log errors here at all? */
219 if (writev(socket, iov, 2) < 0 && errno != ECONNRESET) {
220 perror ("writev");
221 }
222 }
223
224 /*
225 * Handle a SIGTERM
226 */
227
228 static void
229 handleterm(int signal)
230 {
231 (void) signal;
232
233 killpidfile();
234 fflush(NULL);
235 _exit(1);
236 }
237
238 /*
239 * Get rid of our pid file
240 */
241
242 static void
243 killpidfile(void)
244 {
245 if (PIDFN != NULL) {
246 unlink(PIDFN);
247 }
248 }