Testsuite: munge recorded TLS version &c in output to permit awkward test-host instal...
[exim.git] / test / src / server.c
CommitLineData
c55a77db
PH
1/* A little hacked up program that listens on a given port and allows a script
2to play the part of a remote MTA for testing purposes. This scripted version is
3hacked from my original interactive version. A further hack allows it to listen
4on a Unix domain socket as an alternative to a TCP/IP port.
5
6In an IPv6 world, listening happens on both an IPv6 and an IPv4 socket, always
7on all interfaces, unless the option -noipv6 is given. */
8
9/* ANSI C standard includes */
10
11#include <ctype.h>
12#include <signal.h>
13#include <stdarg.h>
14#include <stddef.h>
15#include <stdio.h>
16#include <stdlib.h>
17#include <string.h>
18#include <time.h>
19
20/* Unix includes */
21
22#include <errno.h>
23#include <dirent.h>
24#include <sys/types.h>
25
26#include <netinet/in_systm.h>
27#include <netinet/in.h>
28#include <netinet/ip.h>
29
30#ifdef HAVE_NETINET_IP_VAR_H
31#include <netinet/ip_var.h>
32#endif
33
34#include <netdb.h>
35#include <arpa/inet.h>
36#include <sys/time.h>
37#include <sys/resource.h>
38#include <sys/socket.h>
39#include <sys/un.h>
40#include <sys/stat.h>
41#include <fcntl.h>
42#include <unistd.h>
43#include <utime.h>
44
45#ifdef AF_INET6
46#define HAVE_IPV6 1
47#endif
48
49#ifndef S_ADDR_TYPE
50#define S_ADDR_TYPE u_long
51#endif
52
53
54typedef struct line {
55 struct line *next;
56 char line[1];
57} line;
58
59
60/*************************************************
61* SIGALRM handler - crash out *
62*************************************************/
63
64static void
65sigalrm_handler(int sig)
66{
67sig = sig; /* Keep picky compilers happy */
68printf("\nServer timed out\n");
69exit(99);
70}
71
72
73/*************************************************
74* Get textual IP address *
75*************************************************/
76
77/* This function is copied from Exim */
78
79char *
80host_ntoa(const void *arg, char *buffer)
81{
82char *yield;
83
84/* The new world. It is annoying that we have to fish out the address from
85different places in the block, depending on what kind of address it is. It
86is also a pain that inet_ntop() returns a const char *, whereas the IPv4
87function inet_ntoa() returns just char *, and some picky compilers insist
88on warning if one assigns a const char * to a char *. Hence the casts. */
89
90#if HAVE_IPV6
91char addr_buffer[46];
92int family = ((struct sockaddr *)arg)->sa_family;
93if (family == AF_INET6)
94 {
95 struct sockaddr_in6 *sk = (struct sockaddr_in6 *)arg;
96 yield = (char *)inet_ntop(family, &(sk->sin6_addr), addr_buffer,
97 sizeof(addr_buffer));
98 }
99else
100 {
101 struct sockaddr_in *sk = (struct sockaddr_in *)arg;
102 yield = (char *)inet_ntop(family, &(sk->sin_addr), addr_buffer,
103 sizeof(addr_buffer));
104 }
105
106/* If the result is a mapped IPv4 address, show it in V4 format. */
107
108if (strncmp(yield, "::ffff:", 7) == 0) yield += 7;
109
110#else /* HAVE_IPV6 */
111
112/* The old world */
113
114yield = inet_ntoa(((struct sockaddr_in *)arg)->sin_addr);
115#endif
116
117strcpy(buffer, yield);
118return buffer;
119}
120
121
122/*************************************************
123* Main Program *
124*************************************************/
125
126#define v6n 0 /* IPv6 socket number */
127#define v4n 1 /* IPv4 socket number */
128#define udn 2 /* Unix domain socket number */
129#define skn 2 /* Potential number of sockets */
130
131int main(int argc, char **argv)
132{
133int i;
134int port = 0;
135int listen_socket[3] = { -1, -1, -1 };
136int accept_socket;
137int dup_accept_socket;
138int connection_count = 1;
139int count;
140int on = 1;
141int timeout = 5;
142int use_ipv4 = 1;
143int use_ipv6 = 1;
144int debug = 0;
145int na = 1;
146line *script = NULL;
147line *last = NULL;
148line *s;
149FILE *in, *out;
150
151char *sockname = NULL;
152unsigned char buffer[10240];
153
154struct sockaddr_un sockun; /* don't use "sun" */
155struct sockaddr_un sockun_accepted;
156int sockun_len = sizeof(sockun_accepted);
157
158#if HAVE_IPV6
159struct sockaddr_in6 sin6;
160struct sockaddr_in6 accepted;
161struct in6_addr anyaddr6 = IN6ADDR_ANY_INIT ;
162#else
163struct sockaddr_in accepted;
164#endif
165
166/* Always need an IPv4 structure */
167
168struct sockaddr_in sin4;
169
170int len = sizeof(accepted);
171
172
173/* Sort out the arguments */
174
175while (na < argc && argv[na][0] == '-')
176 {
177 if (strcmp(argv[na], "-d") == 0) debug = 1;
178 else if (strcmp(argv[na], "-t") == 0) timeout = atoi(argv[++na]);
179 else if (strcmp(argv[na], "-noipv4") == 0) use_ipv4 = 0;
180 else if (strcmp(argv[na], "-noipv6") == 0) use_ipv6 = 0;
181 else
182 {
183 printf("server: unknown option %s\n", argv[na]);
184 exit(1);
185 }
186 na++;
187 }
188
189if (!use_ipv4 && !use_ipv6)
190 {
191 printf("server: -noipv4 and -noipv6 cannot both be given\n");
192 exit(1);
193 }
194
195if (na >= argc)
196 {
197 printf("server: no port number or socket name given\n");
198 exit(1);
199 }
200
201if (argv[na][0] == '/')
202 {
203 sockname = argv[na];
204 unlink(sockname); /* in case left lying around */
205 }
206else port = atoi(argv[na]);
207na++;
208
209if (na < argc) connection_count = atoi(argv[na]);
210
211
212/* Create sockets */
213
214if (port == 0) /* Unix domain */
215 {
216 if (debug) printf("Creating Unix domain socket\n");
217 listen_socket[udn] = socket(PF_UNIX, SOCK_STREAM, 0);
218 if (listen_socket[udn] < 0)
219 {
220 printf("Unix domain socket creation failed: %s\n", strerror(errno));
221 exit(1);
222 }
223 }
224else
225 {
226 #if HAVE_IPV6
227 if (use_ipv6)
228 {
229 if (debug) printf("Creating IPv6 socket\n");
230 listen_socket[v6n] = socket(AF_INET6, SOCK_STREAM, 0);
231 if (listen_socket[v6n] < 0)
232 {
233 printf("IPv6 socket creation failed: %s\n", strerror(errno));
234 exit(1);
235 }
236
237 /* If this is an IPv6 wildcard socket, set IPV6_V6ONLY if that option is
238 available. */
239
240 #ifdef IPV6_V6ONLY
241 if (setsockopt(listen_socket[v6n], IPPROTO_IPV6, IPV6_V6ONLY, (char *)(&on),
242 sizeof(on)) < 0)
243 printf("Setting IPV6_V6ONLY on IPv6 wildcard "
244 "socket failed (%s): carrying on without it\n", strerror(errno));
245 #endif /* IPV6_V6ONLY */
246 }
247 #endif /* HAVE_IPV6 */
248
249 /* Create an IPv4 socket if required */
250
251 if (use_ipv4)
252 {
253 if (debug) printf("Creating IPv4 socket\n");
254 listen_socket[v4n] = socket(AF_INET, SOCK_STREAM, 0);
255 if (listen_socket[v4n] < 0)
256 {
257 printf("IPv4 socket creation failed: %s\n", strerror(errno));
258 exit(1);
259 }
260 }
261 }
262
263
264/* Set SO_REUSEADDR on the IP sockets so that the program can be restarted
265while a connection is being handled - this can happen as old connections lie
266around for a bit while crashed processes are tidied away. Without this, a
267connection will prevent reuse of the smtp port for listening. */
268
269for (i = v6n; i <= v4n; i++)
270 {
271 if (listen_socket[i] >= 0 &&
272 setsockopt(listen_socket[i], SOL_SOCKET, SO_REUSEADDR, (char *)(&on),
273 sizeof(on)) < 0)
274 {
275 printf("setting SO_REUSEADDR on socket failed: %s\n", strerror(errno));
276 exit(1);
277 }
278 }
279
280
281/* Now bind the sockets to the required port or path. If a path, ensure
282anyone can write to it. */
283
284if (port == 0)
285 {
286 struct stat statbuf;
287 sockun.sun_family = AF_UNIX;
288 if (debug) printf("Binding Unix domain socket\n");
289 sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1), sockname);
290 if (bind(listen_socket[udn], (struct sockaddr *)&sockun, sizeof(sockun)) < 0)
291 {
292 printf("Unix domain socket bind() failed: %s\n", strerror(errno));
293 exit(1);
294 }
295 (void)stat(sockname, &statbuf);
296 if (debug) printf("Setting Unix domain socket mode: %0x\n",
297 statbuf.st_mode | 0777);
298 if (chmod(sockname, statbuf.st_mode | 0777) < 0)
299 {
300 printf("Unix domain socket chmod() failed: %s\n", strerror(errno));
301 exit(1);
302 }
303 }
304
305else
306 {
307 for (i = 0; i < skn; i++)
308 {
309 if (listen_socket[i] < 0) continue;
310
311 /* For an IPv6 listen, use an IPv6 socket */
312
313 #if HAVE_IPV6
314 if (i == v6n)
315 {
316 memset(&sin6, 0, sizeof(sin6));
317 sin6.sin6_family = AF_INET6;
318 sin6.sin6_port = htons(port);
319 sin6.sin6_addr = anyaddr6;
320 if (bind(listen_socket[i], (struct sockaddr *)&sin6, sizeof(sin6)) < 0)
321 {
322 printf("IPv6 socket bind() failed: %s\n", strerror(errno));
323 exit(1);
324 }
325 }
326 else
327 #endif
328
329 /* For an IPv4 bind, use an IPv4 socket, even in an IPv6 world. If an IPv4
330 bind fails EADDRINUSE after IPv6 success, carry on, because it means the
331 IPv6 socket will handle IPv4 connections. */
332
333 {
334 memset(&sin4, 0, sizeof(sin4));
335 sin4.sin_family = AF_INET;
336 sin4.sin_addr.s_addr = (S_ADDR_TYPE)INADDR_ANY;
337 sin4.sin_port = htons(port);
338 if (bind(listen_socket[i], (struct sockaddr *)&sin4, sizeof(sin4)) < 0)
339 {
340 if (listen_socket[v6n] < 0 || errno != EADDRINUSE)
341 {
342 printf("IPv4 socket bind() failed: %s\n", strerror(errno));
343 exit(1);
344 }
345 else
346 {
347 close(listen_socket[i]);
348 listen_socket[i] = -1;
349 }
350 }
351 }
352 }
353 }
354
355
356/* Start listening. If IPv4 fails EADDRINUSE after IPv6 succeeds, ignore the
357error because it means that the IPv6 socket will handle IPv4 connections. Don't
358output anything, because it will mess up the test output, which will be
359different for systems that do this and those that don't. */
360
361for (i = 0; i <= skn; i++)
362 {
363 if (listen_socket[i] >= 0 && listen(listen_socket[i], 5) < 0)
364 {
365 if (i != v4n || listen_socket[v6n] < 0 || errno != EADDRINUSE)
366 {
367 printf("listen() failed: %s\n", strerror(errno));
368 exit(1);
369 }
370 }
371 }
372
373
374/* This program handles only a fixed number of connections, in sequence. Before
375waiting for the first connection, read the standard input, which contains the
376script of things to do. A line containing "++++" is treated as end of file.
377This is so that the Perl driving script doesn't have to close the pipe -
378because that would cause it to wait for this process, which it doesn't yet want
379to do. The driving script adds the "++++" automatically - it doesn't actually
380appear in the test script. */
381
382while (fgets(buffer, sizeof(buffer), stdin) != NULL)
383 {
384 line *next;
385 int n = (int)strlen(buffer);
386 while (n > 0 && isspace(buffer[n-1])) n--;
387 buffer[n] = 0;
388 if (strcmp(buffer, "++++") == 0) break;
389 next = malloc(sizeof(line) + n);
390 next->next = NULL;
391 strcpy(next->line, buffer);
392 if (last == NULL) script = last = next;
393 else last->next = next;
394 last = next;
395 }
396
397fclose(stdin);
398
399/* SIGALRM handler crashes out */
400
401signal(SIGALRM, sigalrm_handler);
402
403/* s points to the current place in the script */
404
405s = script;
406
407for (count = 0; count < connection_count; count++)
408 {
409 alarm(timeout);
410 if (port <= 0)
411 {
412 printf("Listening on %s ... ", sockname);
413 fflush(stdout);
414 accept_socket = accept(listen_socket[udn],
415 (struct sockaddr *)&sockun_accepted, &sockun_len);
416 }
417
418 else
419 {
420 int lcount;
421 int max_socket = 0;
422 fd_set select_listen;
423
424 printf("Listening on port %d ... ", port);
425 fflush(stdout);
426
427 FD_ZERO(&select_listen);
428 for (i = 0; i < skn; i++)
429 {
430 if (listen_socket[i] >= 0) FD_SET(listen_socket[i], &select_listen);
431 if (listen_socket[i] > max_socket) max_socket = listen_socket[i];
432 }
433
434 lcount = select(max_socket + 1, &select_listen, NULL, NULL, NULL);
435 if (lcount < 0)
436 {
437 printf("Select failed\n");
438 fflush(stdout);
439 continue;
440 }
441
442 accept_socket = -1;
443 for (i = 0; i < skn; i++)
444 {
445 if (listen_socket[i] > 0 && FD_ISSET(listen_socket[i], &select_listen))
446 {
447 accept_socket = accept(listen_socket[i],
448 (struct sockaddr *)&accepted, &len);
449 FD_CLR(listen_socket[i], &select_listen);
450 break;
451 }
452 }
453 }
454 alarm(0);
455
456 if (accept_socket < 0)
457 {
458 printf("accept() failed: %s\n", strerror(errno));
459 exit(1);
460 }
461
462 out = fdopen(accept_socket, "w");
463
464 dup_accept_socket = dup(accept_socket);
465
466 if (port > 0)
467 printf("\nConnection request from [%s]\n", host_ntoa(&accepted, buffer));
468 else
469 {
470 printf("\nConnection request\n");
471
472 /* Linux supports a feature for acquiring the peer's credentials, but it
473 appears to be Linux-specific. This code is untested and unused, just
474 saved here for reference. */
475
476 /**********--------------------
477 struct ucred cr;
478 int cl=sizeof(cr);
479
480 if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl)==0) {
481 printf("Peer's pid=%d, uid=%d, gid=%d\n",
482 cr.pid, cr.uid, cr.gid);
483 --------------*****************/
484 }
485
486 if (dup_accept_socket < 0)
487 {
488 printf("Couldn't dup socket descriptor\n");
489 printf("421 Connection refused: %s\n", strerror(errno));
490 fprintf(out, "421 Connection refused: %s\r\n", strerror(errno));
491 fclose(out);
492 exit(2);
493 }
494
495 in = fdopen(dup_accept_socket, "r");
496
497 /* Loop for handling the conversation(s). For use in SMTP sessions, there are
498 default rules for determining input and output lines: the latter start with
499 digits. This means that the input looks like SMTP dialog. However, this
500 doesn't work for other tests (e.g. ident tests) so we have explicit '<' and
501 '>' flags for input and output as well as the defaults. */
502
503 for (; s != NULL; s = s->next)
504 {
505 char *ss = s->line;
506
507 /* Output lines either start with '>' or a digit. In the '>' case we can
508 fudge the sending of \r\n as required. Default is \r\n, ">>" send nothing,
509 ">CR>" sends \r only, and ">LF>" sends \n only. We can also force a
510 connection closedown by ">*eof". */
511
512 if (ss[0] == '>')
513 {
514 char *end = "\r\n";
515 printf("%s\n", ss++);
516
517 if (strncmp(ss, "*eof", 4) == 0)
518 {
519 s = s->next;
520 goto END_OFF;
521 }
522
523 if (*ss == '>')
524 { end = ""; ss++; }
525 else if (strncmp(ss, "CR>", 3) == 0)
526 { end = "\r"; ss += 3; }
527 else if (strncmp(ss, "LF>", 3) == 0)
528 { end = "\n"; ss += 3; }
529
530 fprintf(out, "%s%s", ss, end);
531 }
532
533 else if (isdigit((unsigned char)ss[0]))
534 {
535 printf("%s\n", ss);
536 fprintf(out, "%s\r\n", ss);
537 }
538
539 /* If the script line starts with "*sleep" we just sleep for a while
540 before continuing. */
541
542 else if (strncmp(ss, "*sleep ", 7) == 0)
543 {
544 int sleepfor = atoi(ss+7);
545 printf("%s\n", ss);
546 fflush(out);
547 sleep(sleepfor);
548 }
549
550 /* Otherwise the script line is the start of an input line we are expecting
551 from the client, or "*eof" indicating we expect the client to close the
552 connection. Read command line or data lines; the latter are indicated
553 by the expected line being just ".". If the line starts with '<', that
554 doesn't form part of the expected input. (This allows for incoming data
555 starting with a digit.) */
556
557 else
558 {
559 int offset;
560 int data = strcmp(ss, ".") == 0;
561
562 if (ss[0] == '<')
563 {
564 buffer[0] = '<';
565 offset = 1;
566 }
567 else offset = 0;
568
569 fflush(out);
570
571 for (;;)
572 {
573 int n;
574 alarm(timeout);
575 if (fgets(buffer+offset, sizeof(buffer)-offset, in) == NULL)
576 {
577 printf("%sxpected EOF read from client\n",
578 (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
579 s = s->next;
580 goto END_OFF;
581 }
582 alarm(0);
583 n = (int)strlen(buffer);
584 while (n > 0 && isspace(buffer[n-1])) n--;
585 buffer[n] = 0;
586 printf("%s\n", buffer);
587 if (!data || strcmp(buffer, ".") == 0) break;
588 }
589
590 if (strncmp(ss, buffer, (int)strlen(ss)) != 0)
591 {
592 printf("Comparison failed - bailing out\n");
593 printf("Expected: %s\n", ss);
594 break;
595 }
596 }
597 }
598
599 END_OFF:
600 fclose(in);
601 fclose(out);
602 }
603
604if (s == NULL) printf("End of script\n");
605
606if (sockname != NULL) unlink(sockname);
607exit(0);
608}
609
610/* End of server.c */