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