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