Adjust the timeout after interrupted select()
[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
a719fce4 46# define HAVE_IPV6 1
c55a77db
PH
47#endif
48
49#ifndef S_ADDR_TYPE
a719fce4
JH
50# define S_ADDR_TYPE u_long
51#endif
52
53#ifndef CS
54# define CS (char *)
c55a77db
PH
55#endif
56
57
58typedef struct line {
59 struct line *next;
7eb6c37c 60 unsigned len;
c55a77db
PH
61 char line[1];
62} line;
63
64
65/*************************************************
66* SIGALRM handler - crash out *
67*************************************************/
68
69static void
70sigalrm_handler(int sig)
71{
72sig = sig; /* Keep picky compilers happy */
73printf("\nServer timed out\n");
74exit(99);
75}
76
77
78/*************************************************
79* Get textual IP address *
80*************************************************/
81
82/* This function is copied from Exim */
83
84char *
85host_ntoa(const void *arg, char *buffer)
86{
87char *yield;
88
89/* The new world. It is annoying that we have to fish out the address from
90different places in the block, depending on what kind of address it is. It
91is also a pain that inet_ntop() returns a const char *, whereas the IPv4
92function inet_ntoa() returns just char *, and some picky compilers insist
93on warning if one assigns a const char * to a char *. Hence the casts. */
94
95#if HAVE_IPV6
96char addr_buffer[46];
97int family = ((struct sockaddr *)arg)->sa_family;
98if (family == AF_INET6)
99 {
100 struct sockaddr_in6 *sk = (struct sockaddr_in6 *)arg;
101 yield = (char *)inet_ntop(family, &(sk->sin6_addr), addr_buffer,
102 sizeof(addr_buffer));
103 }
104else
105 {
106 struct sockaddr_in *sk = (struct sockaddr_in *)arg;
107 yield = (char *)inet_ntop(family, &(sk->sin_addr), addr_buffer,
108 sizeof(addr_buffer));
109 }
110
111/* If the result is a mapped IPv4 address, show it in V4 format. */
112
113if (strncmp(yield, "::ffff:", 7) == 0) yield += 7;
114
115#else /* HAVE_IPV6 */
116
117/* The old world */
118
119yield = inet_ntoa(((struct sockaddr_in *)arg)->sin_addr);
120#endif
121
122strcpy(buffer, yield);
123return buffer;
124}
125
126
7eb6c37c
JH
127
128static void
129printit(char * s, int n)
130{
131while(n--)
132 {
133 unsigned char c = *s++;
134 if (c == '\\')
135 printf("\\\\");
136 else if (c >= ' ' && c <= '~') /* assumes ascii */
137 putchar(c);
138 else
139 printf("\\x%02x", c);
140 }
141putchar('\n');
142}
143
144
145
c55a77db
PH
146/*************************************************
147* Main Program *
148*************************************************/
149
150#define v6n 0 /* IPv6 socket number */
151#define v4n 1 /* IPv4 socket number */
152#define udn 2 /* Unix domain socket number */
153#define skn 2 /* Potential number of sockets */
154
155int main(int argc, char **argv)
156{
157int i;
158int port = 0;
159int listen_socket[3] = { -1, -1, -1 };
160int accept_socket;
161int dup_accept_socket;
162int connection_count = 1;
163int count;
164int on = 1;
165int timeout = 5;
8a512ed5 166int initial_pause = 0;
c55a77db
PH
167int use_ipv4 = 1;
168int use_ipv6 = 1;
169int debug = 0;
170int na = 1;
171line *script = NULL;
172line *last = NULL;
173line *s;
174FILE *in, *out;
7eb6c37c 175int linebuf = 1;
f41e0506 176char *pidfile = NULL;
c55a77db
PH
177
178char *sockname = NULL;
179unsigned char buffer[10240];
180
181struct sockaddr_un sockun; /* don't use "sun" */
182struct sockaddr_un sockun_accepted;
183int sockun_len = sizeof(sockun_accepted);
184
185#if HAVE_IPV6
186struct sockaddr_in6 sin6;
187struct sockaddr_in6 accepted;
188struct in6_addr anyaddr6 = IN6ADDR_ANY_INIT ;
189#else
190struct sockaddr_in accepted;
191#endif
192
193/* Always need an IPv4 structure */
194
195struct sockaddr_in sin4;
196
197int len = sizeof(accepted);
198
199
200/* Sort out the arguments */
201
202while (na < argc && argv[na][0] == '-')
203 {
204 if (strcmp(argv[na], "-d") == 0) debug = 1;
205 else if (strcmp(argv[na], "-t") == 0) timeout = atoi(argv[++na]);
8a512ed5 206 else if (strcmp(argv[na], "-i") == 0) initial_pause = atoi(argv[++na]);
c55a77db
PH
207 else if (strcmp(argv[na], "-noipv4") == 0) use_ipv4 = 0;
208 else if (strcmp(argv[na], "-noipv6") == 0) use_ipv6 = 0;
f41e0506 209 else if (strcmp(argv[na], "-oP") == 0) pidfile = argv[++na];
c55a77db
PH
210 else
211 {
212 printf("server: unknown option %s\n", argv[na]);
213 exit(1);
214 }
215 na++;
216 }
217
218if (!use_ipv4 && !use_ipv6)
219 {
220 printf("server: -noipv4 and -noipv6 cannot both be given\n");
221 exit(1);
222 }
223
224if (na >= argc)
225 {
226 printf("server: no port number or socket name given\n");
227 exit(1);
228 }
229
230if (argv[na][0] == '/')
231 {
232 sockname = argv[na];
233 unlink(sockname); /* in case left lying around */
234 }
235else port = atoi(argv[na]);
236na++;
237
238if (na < argc) connection_count = atoi(argv[na]);
239
240
8a512ed5
JH
241/* Initial pause (before creating listen sockets */
242if (initial_pause > 0)
243 {
244 if (debug)
245 printf("%d: Inital pause of %d seconds\n", time(NULL), initial_pause);
246 else
247 printf("Inital pause of %d seconds\n", initial_pause);
248 while (initial_pause > 0)
249 initial_pause = sleep(initial_pause);
250 }
251
c55a77db
PH
252/* Create sockets */
253
254if (port == 0) /* Unix domain */
255 {
8a512ed5 256 if (debug) printf("%d: Creating Unix domain socket\n", time(NULL));
c55a77db
PH
257 listen_socket[udn] = socket(PF_UNIX, SOCK_STREAM, 0);
258 if (listen_socket[udn] < 0)
259 {
260 printf("Unix domain socket creation failed: %s\n", strerror(errno));
261 exit(1);
262 }
263 }
264else
265 {
266 #if HAVE_IPV6
267 if (use_ipv6)
268 {
269 if (debug) printf("Creating IPv6 socket\n");
270 listen_socket[v6n] = socket(AF_INET6, SOCK_STREAM, 0);
271 if (listen_socket[v6n] < 0)
272 {
273 printf("IPv6 socket creation failed: %s\n", strerror(errno));
274 exit(1);
275 }
276
277 /* If this is an IPv6 wildcard socket, set IPV6_V6ONLY if that option is
278 available. */
279
280 #ifdef IPV6_V6ONLY
281 if (setsockopt(listen_socket[v6n], IPPROTO_IPV6, IPV6_V6ONLY, (char *)(&on),
282 sizeof(on)) < 0)
283 printf("Setting IPV6_V6ONLY on IPv6 wildcard "
284 "socket failed (%s): carrying on without it\n", strerror(errno));
285 #endif /* IPV6_V6ONLY */
286 }
287 #endif /* HAVE_IPV6 */
288
289 /* Create an IPv4 socket if required */
290
291 if (use_ipv4)
292 {
293 if (debug) printf("Creating IPv4 socket\n");
294 listen_socket[v4n] = socket(AF_INET, SOCK_STREAM, 0);
295 if (listen_socket[v4n] < 0)
296 {
297 printf("IPv4 socket creation failed: %s\n", strerror(errno));
298 exit(1);
299 }
300 }
301 }
302
303
304/* Set SO_REUSEADDR on the IP sockets so that the program can be restarted
305while a connection is being handled - this can happen as old connections lie
306around for a bit while crashed processes are tidied away. Without this, a
307connection will prevent reuse of the smtp port for listening. */
308
309for (i = v6n; i <= v4n; i++)
310 {
311 if (listen_socket[i] >= 0 &&
312 setsockopt(listen_socket[i], SOL_SOCKET, SO_REUSEADDR, (char *)(&on),
313 sizeof(on)) < 0)
314 {
315 printf("setting SO_REUSEADDR on socket failed: %s\n", strerror(errno));
316 exit(1);
317 }
318 }
319
320
321/* Now bind the sockets to the required port or path. If a path, ensure
322anyone can write to it. */
323
324if (port == 0)
325 {
326 struct stat statbuf;
327 sockun.sun_family = AF_UNIX;
328 if (debug) printf("Binding Unix domain socket\n");
329 sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1), sockname);
330 if (bind(listen_socket[udn], (struct sockaddr *)&sockun, sizeof(sockun)) < 0)
331 {
332 printf("Unix domain socket bind() failed: %s\n", strerror(errno));
333 exit(1);
334 }
335 (void)stat(sockname, &statbuf);
336 if (debug) printf("Setting Unix domain socket mode: %0x\n",
337 statbuf.st_mode | 0777);
338 if (chmod(sockname, statbuf.st_mode | 0777) < 0)
339 {
340 printf("Unix domain socket chmod() failed: %s\n", strerror(errno));
341 exit(1);
342 }
343 }
344
345else
346 {
347 for (i = 0; i < skn; i++)
348 {
349 if (listen_socket[i] < 0) continue;
350
351 /* For an IPv6 listen, use an IPv6 socket */
352
353 #if HAVE_IPV6
354 if (i == v6n)
355 {
356 memset(&sin6, 0, sizeof(sin6));
357 sin6.sin6_family = AF_INET6;
358 sin6.sin6_port = htons(port);
359 sin6.sin6_addr = anyaddr6;
360 if (bind(listen_socket[i], (struct sockaddr *)&sin6, sizeof(sin6)) < 0)
361 {
362 printf("IPv6 socket bind() failed: %s\n", strerror(errno));
363 exit(1);
364 }
365 }
366 else
367 #endif
368
369 /* For an IPv4 bind, use an IPv4 socket, even in an IPv6 world. If an IPv4
370 bind fails EADDRINUSE after IPv6 success, carry on, because it means the
371 IPv6 socket will handle IPv4 connections. */
372
373 {
374 memset(&sin4, 0, sizeof(sin4));
375 sin4.sin_family = AF_INET;
376 sin4.sin_addr.s_addr = (S_ADDR_TYPE)INADDR_ANY;
377 sin4.sin_port = htons(port);
378 if (bind(listen_socket[i], (struct sockaddr *)&sin4, sizeof(sin4)) < 0)
379 {
380 if (listen_socket[v6n] < 0 || errno != EADDRINUSE)
381 {
382 printf("IPv4 socket bind() failed: %s\n", strerror(errno));
383 exit(1);
384 }
385 else
386 {
387 close(listen_socket[i]);
388 listen_socket[i] = -1;
389 }
390 }
391 }
392 }
393 }
394
395
396/* Start listening. If IPv4 fails EADDRINUSE after IPv6 succeeds, ignore the
397error because it means that the IPv6 socket will handle IPv4 connections. Don't
398output anything, because it will mess up the test output, which will be
399different for systems that do this and those that don't. */
400
401for (i = 0; i <= skn; i++)
402 {
403 if (listen_socket[i] >= 0 && listen(listen_socket[i], 5) < 0)
404 {
405 if (i != v4n || listen_socket[v6n] < 0 || errno != EADDRINUSE)
406 {
407 printf("listen() failed: %s\n", strerror(errno));
408 exit(1);
409 }
410 }
411 }
412
413
f41e0506
JH
414if (pidfile)
415 {
416 FILE * p;
417 if (!(p = fopen(pidfile, "w")))
418 {
419 fprintf(stderr, "pidfile create failed: %s\n", strerror(errno));
420 exit(1);
421 }
422 fprintf(p, "%ld\n", (long)getpid());
423 fclose(p);
424 }
425
c55a77db
PH
426/* This program handles only a fixed number of connections, in sequence. Before
427waiting for the first connection, read the standard input, which contains the
428script of things to do. A line containing "++++" is treated as end of file.
429This is so that the Perl driving script doesn't have to close the pipe -
430because that would cause it to wait for this process, which it doesn't yet want
431to do. The driving script adds the "++++" automatically - it doesn't actually
7eb6c37c 432appear in the test script. Within lines we interpret \xNN and \\ groups */
c55a77db 433
a719fce4 434while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
c55a77db
PH
435 {
436 line *next;
7eb6c37c 437 char * d;
a719fce4 438 int n = (int)strlen(CS buffer);
7eb6c37c
JH
439
440 if (n > 1 && buffer[0] == '>' && buffer[1] == '>')
441 linebuf = 0;
c55a77db
PH
442 while (n > 0 && isspace(buffer[n-1])) n--;
443 buffer[n] = 0;
a719fce4 444 if (strcmp(CS buffer, "++++") == 0) break;
c55a77db
PH
445 next = malloc(sizeof(line) + n);
446 next->next = NULL;
7eb6c37c
JH
447 d = next->line;
448 {
449 char * s = CS buffer;
450 do
451 {
452 char ch;
453 char cl = *s;
454 if (cl == '\\' && (cl = *++s) == 'x')
455 {
456 if ((ch = *++s - '0') > 9 && (ch -= 'A'-'9'-1) > 15) ch -= 'a'-'A';
457 if ((cl = *++s - '0') > 9 && (cl -= 'A'-'9'-1) > 15) cl -= 'a'-'A';
458 cl |= ch << 4;
459 }
460 *d++ = cl;
461 }
462 while (*s++);
463 }
464 next->len = d - next->line - 1;
c55a77db
PH
465 if (last == NULL) script = last = next;
466 else last->next = next;
467 last = next;
468 }
469
470fclose(stdin);
471
472/* SIGALRM handler crashes out */
473
474signal(SIGALRM, sigalrm_handler);
475
476/* s points to the current place in the script */
477
478s = script;
479
480for (count = 0; count < connection_count; count++)
481 {
482 alarm(timeout);
483 if (port <= 0)
484 {
485 printf("Listening on %s ... ", sockname);
486 fflush(stdout);
487 accept_socket = accept(listen_socket[udn],
488 (struct sockaddr *)&sockun_accepted, &sockun_len);
489 }
490
491 else
492 {
493 int lcount;
494 int max_socket = 0;
495 fd_set select_listen;
496
497 printf("Listening on port %d ... ", port);
498 fflush(stdout);
499
500 FD_ZERO(&select_listen);
501 for (i = 0; i < skn; i++)
502 {
503 if (listen_socket[i] >= 0) FD_SET(listen_socket[i], &select_listen);
504 if (listen_socket[i] > max_socket) max_socket = listen_socket[i];
505 }
506
507 lcount = select(max_socket + 1, &select_listen, NULL, NULL, NULL);
508 if (lcount < 0)
509 {
510 printf("Select failed\n");
511 fflush(stdout);
512 continue;
513 }
514
515 accept_socket = -1;
516 for (i = 0; i < skn; i++)
517 {
518 if (listen_socket[i] > 0 && FD_ISSET(listen_socket[i], &select_listen))
519 {
520 accept_socket = accept(listen_socket[i],
521 (struct sockaddr *)&accepted, &len);
522 FD_CLR(listen_socket[i], &select_listen);
523 break;
524 }
525 }
526 }
527 alarm(0);
528
529 if (accept_socket < 0)
530 {
531 printf("accept() failed: %s\n", strerror(errno));
532 exit(1);
533 }
534
535 out = fdopen(accept_socket, "w");
536
537 dup_accept_socket = dup(accept_socket);
538
539 if (port > 0)
a719fce4 540 printf("\nConnection request from [%s]\n", host_ntoa(&accepted, CS buffer));
c55a77db
PH
541 else
542 {
543 printf("\nConnection request\n");
544
545 /* Linux supports a feature for acquiring the peer's credentials, but it
546 appears to be Linux-specific. This code is untested and unused, just
547 saved here for reference. */
548
549 /**********--------------------
550 struct ucred cr;
551 int cl=sizeof(cr);
552
553 if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl)==0) {
554 printf("Peer's pid=%d, uid=%d, gid=%d\n",
555 cr.pid, cr.uid, cr.gid);
556 --------------*****************/
557 }
558
559 if (dup_accept_socket < 0)
560 {
561 printf("Couldn't dup socket descriptor\n");
562 printf("421 Connection refused: %s\n", strerror(errno));
563 fprintf(out, "421 Connection refused: %s\r\n", strerror(errno));
564 fclose(out);
565 exit(2);
566 }
567
568 in = fdopen(dup_accept_socket, "r");
569
570 /* Loop for handling the conversation(s). For use in SMTP sessions, there are
571 default rules for determining input and output lines: the latter start with
572 digits. This means that the input looks like SMTP dialog. However, this
573 doesn't work for other tests (e.g. ident tests) so we have explicit '<' and
574 '>' flags for input and output as well as the defaults. */
575
576 for (; s != NULL; s = s->next)
577 {
578 char *ss = s->line;
579
580 /* Output lines either start with '>' or a digit. In the '>' case we can
581 fudge the sending of \r\n as required. Default is \r\n, ">>" send nothing,
582 ">CR>" sends \r only, and ">LF>" sends \n only. We can also force a
583 connection closedown by ">*eof". */
584
585 if (ss[0] == '>')
586 {
587 char *end = "\r\n";
7eb6c37c
JH
588 unsigned len = s->len;
589 printit(ss++, len--);
c55a77db
PH
590
591 if (strncmp(ss, "*eof", 4) == 0)
592 {
593 s = s->next;
594 goto END_OFF;
595 }
596
597 if (*ss == '>')
7eb6c37c 598 { end = ""; ss++; len--; }
c55a77db 599 else if (strncmp(ss, "CR>", 3) == 0)
7eb6c37c 600 { end = "\r"; ss += 3; len -= 3; }
c55a77db 601 else if (strncmp(ss, "LF>", 3) == 0)
7eb6c37c 602 { end = "\n"; ss += 3; len -= 3; }
c55a77db 603
7eb6c37c
JH
604 fwrite(ss, 1, len, out);
605 if (*end) fprintf(out, end);
c55a77db
PH
606 }
607
608 else if (isdigit((unsigned char)ss[0]))
609 {
610 printf("%s\n", ss);
611 fprintf(out, "%s\r\n", ss);
612 }
613
614 /* If the script line starts with "*sleep" we just sleep for a while
615 before continuing. */
616
617 else if (strncmp(ss, "*sleep ", 7) == 0)
618 {
619 int sleepfor = atoi(ss+7);
620 printf("%s\n", ss);
621 fflush(out);
622 sleep(sleepfor);
623 }
624
625 /* Otherwise the script line is the start of an input line we are expecting
626 from the client, or "*eof" indicating we expect the client to close the
627 connection. Read command line or data lines; the latter are indicated
628 by the expected line being just ".". If the line starts with '<', that
629 doesn't form part of the expected input. (This allows for incoming data
7eb6c37c
JH
630 starting with a digit.) If the line starts with '<<' we operate in
631 unbuffered rather than line mode and assume that a single read gets the
632 entire message. */
c55a77db
PH
633
634 else
635 {
636 int offset;
637 int data = strcmp(ss, ".") == 0;
638
7eb6c37c
JH
639 if (ss[0] != '<')
640 offset = 0;
641 else
c55a77db
PH
642 {
643 buffer[0] = '<';
7eb6c37c
JH
644 if (ss[1] != '<')
645 offset = 1;
646 else
647 {
648 buffer[1] = '<';
649 offset = 2;
650 }
c55a77db 651 }
c55a77db
PH
652
653 fflush(out);
654
7eb6c37c
JH
655 if (!linebuf)
656 {
657 int n;
658 char c;
659
660 alarm(timeout);
661 n = read(dup_accept_socket, CS buffer+offset, s->len - offset);
662 if (n == 0)
663 {
664 printf("%sxpected EOF read from client\n",
665 (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
666 s = s->next;
667 goto END_OFF;
668 }
669 if (offset != 2)
670 while (read(dup_accept_socket, &c, 1) == 1 && c != '\n') ;
671 alarm(0);
672 n += offset;
673
674 printit(buffer, n);
675
676 if (data) do
677 {
678 n = (read(dup_accept_socket, &c, 1) == 1 && c == '.');
679 while (c != '\n' && read(dup_accept_socket, &c, 1) == 1)
680 ;
681 } while (!n);
682 else if (memcmp(ss, buffer, n) != 0)
683 {
684 printf("Comparison failed - bailing out\nExpected: ");
685 printit(ss, n);
686 break;
687 }
688 }
689 else
690 {
691 for (;;)
692 {
693 int n;
694 alarm(timeout);
695 if (fgets(CS buffer+offset, sizeof(buffer)-offset, in) == NULL)
696 {
697 printf("%sxpected EOF read from client\n",
698 (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
699 s = s->next;
700 goto END_OFF;
701 }
702 alarm(0);
703 n = (int)strlen(CS buffer);
704 while (n > 0 && isspace(buffer[n-1])) n--;
705 buffer[n] = 0;
706 printf("%s\n", buffer);
707 if (!data || strcmp(CS buffer, ".") == 0) break;
708 }
709
710 if (strncmp(ss, CS buffer, (int)strlen(ss)) != 0)
711 {
712 printf("Comparison failed - bailing out\n");
713 printf("Expected: %s\n", ss);
714 break;
715 }
716 }
c55a77db
PH
717 }
718 }
719
720 END_OFF:
721 fclose(in);
722 fclose(out);
723 }
724
725if (s == NULL) printf("End of script\n");
726
727if (sockname != NULL) unlink(sockname);
728exit(0);
729}
730
731/* End of server.c */