Testsuite: enforce different exim/testuser group numbers
[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 */
e5c9fb3c
HSHR
201if (argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")))
202 {
214133f2 203 printf("Usage: %s [options] port|socket [connection count]\n", argv[0]);
e5c9fb3c
HSHR
204 puts("Options"
205 "\n\t-d debug"
206 "\n\t-i n n seconds initial delay"
207 "\n\t-noipv4 disable ipv4"
208 "\n\t-noipv6 disable ipv6"
209 "\n\t-oP file write PID to file"
210 "\n\t-t n n seconds timeout"
211 );
212 exit(0);
213 }
c55a77db
PH
214
215while (na < argc && argv[na][0] == '-')
216 {
217 if (strcmp(argv[na], "-d") == 0) debug = 1;
218 else if (strcmp(argv[na], "-t") == 0) timeout = atoi(argv[++na]);
8a512ed5 219 else if (strcmp(argv[na], "-i") == 0) initial_pause = atoi(argv[++na]);
c55a77db
PH
220 else if (strcmp(argv[na], "-noipv4") == 0) use_ipv4 = 0;
221 else if (strcmp(argv[na], "-noipv6") == 0) use_ipv6 = 0;
f41e0506 222 else if (strcmp(argv[na], "-oP") == 0) pidfile = argv[++na];
c55a77db
PH
223 else
224 {
e5c9fb3c 225 printf("server: unknown option %s, try -h or --help\n", argv[na]);
c55a77db
PH
226 exit(1);
227 }
228 na++;
229 }
230
231if (!use_ipv4 && !use_ipv6)
232 {
233 printf("server: -noipv4 and -noipv6 cannot both be given\n");
234 exit(1);
235 }
236
237if (na >= argc)
238 {
239 printf("server: no port number or socket name given\n");
240 exit(1);
241 }
242
243if (argv[na][0] == '/')
244 {
245 sockname = argv[na];
246 unlink(sockname); /* in case left lying around */
247 }
248else port = atoi(argv[na]);
249na++;
250
251if (na < argc) connection_count = atoi(argv[na]);
252
253
8a512ed5
JH
254/* Initial pause (before creating listen sockets */
255if (initial_pause > 0)
256 {
257 if (debug)
258 printf("%d: Inital pause of %d seconds\n", time(NULL), initial_pause);
259 else
260 printf("Inital pause of %d seconds\n", initial_pause);
261 while (initial_pause > 0)
262 initial_pause = sleep(initial_pause);
263 }
264
c55a77db
PH
265/* Create sockets */
266
267if (port == 0) /* Unix domain */
268 {
8a512ed5 269 if (debug) printf("%d: Creating Unix domain socket\n", time(NULL));
c55a77db
PH
270 listen_socket[udn] = socket(PF_UNIX, SOCK_STREAM, 0);
271 if (listen_socket[udn] < 0)
272 {
273 printf("Unix domain socket creation failed: %s\n", strerror(errno));
274 exit(1);
275 }
276 }
277else
278 {
279 #if HAVE_IPV6
280 if (use_ipv6)
281 {
282 if (debug) printf("Creating IPv6 socket\n");
283 listen_socket[v6n] = socket(AF_INET6, SOCK_STREAM, 0);
284 if (listen_socket[v6n] < 0)
285 {
286 printf("IPv6 socket creation failed: %s\n", strerror(errno));
287 exit(1);
288 }
289
290 /* If this is an IPv6 wildcard socket, set IPV6_V6ONLY if that option is
291 available. */
292
293 #ifdef IPV6_V6ONLY
294 if (setsockopt(listen_socket[v6n], IPPROTO_IPV6, IPV6_V6ONLY, (char *)(&on),
295 sizeof(on)) < 0)
296 printf("Setting IPV6_V6ONLY on IPv6 wildcard "
297 "socket failed (%s): carrying on without it\n", strerror(errno));
298 #endif /* IPV6_V6ONLY */
299 }
300 #endif /* HAVE_IPV6 */
301
302 /* Create an IPv4 socket if required */
303
304 if (use_ipv4)
305 {
306 if (debug) printf("Creating IPv4 socket\n");
307 listen_socket[v4n] = socket(AF_INET, SOCK_STREAM, 0);
308 if (listen_socket[v4n] < 0)
309 {
310 printf("IPv4 socket creation failed: %s\n", strerror(errno));
311 exit(1);
312 }
313 }
314 }
315
316
317/* Set SO_REUSEADDR on the IP sockets so that the program can be restarted
318while a connection is being handled - this can happen as old connections lie
319around for a bit while crashed processes are tidied away. Without this, a
320connection will prevent reuse of the smtp port for listening. */
321
322for (i = v6n; i <= v4n; i++)
323 {
324 if (listen_socket[i] >= 0 &&
325 setsockopt(listen_socket[i], SOL_SOCKET, SO_REUSEADDR, (char *)(&on),
326 sizeof(on)) < 0)
327 {
328 printf("setting SO_REUSEADDR on socket failed: %s\n", strerror(errno));
329 exit(1);
330 }
331 }
332
333
334/* Now bind the sockets to the required port or path. If a path, ensure
335anyone can write to it. */
336
337if (port == 0)
338 {
339 struct stat statbuf;
340 sockun.sun_family = AF_UNIX;
341 if (debug) printf("Binding Unix domain socket\n");
342 sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1), sockname);
343 if (bind(listen_socket[udn], (struct sockaddr *)&sockun, sizeof(sockun)) < 0)
344 {
345 printf("Unix domain socket bind() failed: %s\n", strerror(errno));
346 exit(1);
347 }
348 (void)stat(sockname, &statbuf);
349 if (debug) printf("Setting Unix domain socket mode: %0x\n",
350 statbuf.st_mode | 0777);
351 if (chmod(sockname, statbuf.st_mode | 0777) < 0)
352 {
353 printf("Unix domain socket chmod() failed: %s\n", strerror(errno));
354 exit(1);
355 }
356 }
357
358else
359 {
360 for (i = 0; i < skn; i++)
361 {
362 if (listen_socket[i] < 0) continue;
363
364 /* For an IPv6 listen, use an IPv6 socket */
365
366 #if HAVE_IPV6
367 if (i == v6n)
368 {
369 memset(&sin6, 0, sizeof(sin6));
370 sin6.sin6_family = AF_INET6;
371 sin6.sin6_port = htons(port);
372 sin6.sin6_addr = anyaddr6;
373 if (bind(listen_socket[i], (struct sockaddr *)&sin6, sizeof(sin6)) < 0)
374 {
375 printf("IPv6 socket bind() failed: %s\n", strerror(errno));
376 exit(1);
377 }
378 }
379 else
380 #endif
381
382 /* For an IPv4 bind, use an IPv4 socket, even in an IPv6 world. If an IPv4
383 bind fails EADDRINUSE after IPv6 success, carry on, because it means the
384 IPv6 socket will handle IPv4 connections. */
385
386 {
387 memset(&sin4, 0, sizeof(sin4));
388 sin4.sin_family = AF_INET;
389 sin4.sin_addr.s_addr = (S_ADDR_TYPE)INADDR_ANY;
390 sin4.sin_port = htons(port);
391 if (bind(listen_socket[i], (struct sockaddr *)&sin4, sizeof(sin4)) < 0)
392 {
393 if (listen_socket[v6n] < 0 || errno != EADDRINUSE)
394 {
395 printf("IPv4 socket bind() failed: %s\n", strerror(errno));
396 exit(1);
397 }
398 else
399 {
400 close(listen_socket[i]);
401 listen_socket[i] = -1;
402 }
403 }
404 }
405 }
406 }
407
408
409/* Start listening. If IPv4 fails EADDRINUSE after IPv6 succeeds, ignore the
410error because it means that the IPv6 socket will handle IPv4 connections. Don't
411output anything, because it will mess up the test output, which will be
412different for systems that do this and those that don't. */
413
414for (i = 0; i <= skn; i++)
415 {
416 if (listen_socket[i] >= 0 && listen(listen_socket[i], 5) < 0)
417 {
418 if (i != v4n || listen_socket[v6n] < 0 || errno != EADDRINUSE)
419 {
420 printf("listen() failed: %s\n", strerror(errno));
421 exit(1);
422 }
423 }
424 }
425
426
f41e0506
JH
427if (pidfile)
428 {
429 FILE * p;
430 if (!(p = fopen(pidfile, "w")))
431 {
432 fprintf(stderr, "pidfile create failed: %s\n", strerror(errno));
433 exit(1);
434 }
435 fprintf(p, "%ld\n", (long)getpid());
436 fclose(p);
437 }
438
c55a77db
PH
439/* This program handles only a fixed number of connections, in sequence. Before
440waiting for the first connection, read the standard input, which contains the
441script of things to do. A line containing "++++" is treated as end of file.
442This is so that the Perl driving script doesn't have to close the pipe -
443because that would cause it to wait for this process, which it doesn't yet want
444to do. The driving script adds the "++++" automatically - it doesn't actually
7eb6c37c 445appear in the test script. Within lines we interpret \xNN and \\ groups */
c55a77db 446
a719fce4 447while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
c55a77db
PH
448 {
449 line *next;
7eb6c37c 450 char * d;
a719fce4 451 int n = (int)strlen(CS buffer);
7eb6c37c
JH
452
453 if (n > 1 && buffer[0] == '>' && buffer[1] == '>')
454 linebuf = 0;
c55a77db
PH
455 while (n > 0 && isspace(buffer[n-1])) n--;
456 buffer[n] = 0;
a719fce4 457 if (strcmp(CS buffer, "++++") == 0) break;
c55a77db
PH
458 next = malloc(sizeof(line) + n);
459 next->next = NULL;
7eb6c37c
JH
460 d = next->line;
461 {
462 char * s = CS buffer;
463 do
464 {
465 char ch;
466 char cl = *s;
467 if (cl == '\\' && (cl = *++s) == 'x')
468 {
469 if ((ch = *++s - '0') > 9 && (ch -= 'A'-'9'-1) > 15) ch -= 'a'-'A';
470 if ((cl = *++s - '0') > 9 && (cl -= 'A'-'9'-1) > 15) cl -= 'a'-'A';
471 cl |= ch << 4;
472 }
473 *d++ = cl;
474 }
475 while (*s++);
476 }
477 next->len = d - next->line - 1;
c55a77db
PH
478 if (last == NULL) script = last = next;
479 else last->next = next;
480 last = next;
481 }
482
483fclose(stdin);
484
485/* SIGALRM handler crashes out */
486
487signal(SIGALRM, sigalrm_handler);
488
489/* s points to the current place in the script */
490
491s = script;
492
493for (count = 0; count < connection_count; count++)
494 {
495 alarm(timeout);
496 if (port <= 0)
497 {
498 printf("Listening on %s ... ", sockname);
499 fflush(stdout);
500 accept_socket = accept(listen_socket[udn],
501 (struct sockaddr *)&sockun_accepted, &sockun_len);
502 }
503
504 else
505 {
506 int lcount;
507 int max_socket = 0;
508 fd_set select_listen;
509
510 printf("Listening on port %d ... ", port);
511 fflush(stdout);
512
513 FD_ZERO(&select_listen);
514 for (i = 0; i < skn; i++)
515 {
516 if (listen_socket[i] >= 0) FD_SET(listen_socket[i], &select_listen);
517 if (listen_socket[i] > max_socket) max_socket = listen_socket[i];
518 }
519
520 lcount = select(max_socket + 1, &select_listen, NULL, NULL, NULL);
521 if (lcount < 0)
522 {
523 printf("Select failed\n");
524 fflush(stdout);
525 continue;
526 }
527
528 accept_socket = -1;
529 for (i = 0; i < skn; i++)
530 {
531 if (listen_socket[i] > 0 && FD_ISSET(listen_socket[i], &select_listen))
532 {
533 accept_socket = accept(listen_socket[i],
534 (struct sockaddr *)&accepted, &len);
535 FD_CLR(listen_socket[i], &select_listen);
536 break;
537 }
538 }
539 }
540 alarm(0);
541
542 if (accept_socket < 0)
543 {
544 printf("accept() failed: %s\n", strerror(errno));
545 exit(1);
546 }
547
548 out = fdopen(accept_socket, "w");
549
550 dup_accept_socket = dup(accept_socket);
551
552 if (port > 0)
a719fce4 553 printf("\nConnection request from [%s]\n", host_ntoa(&accepted, CS buffer));
c55a77db
PH
554 else
555 {
556 printf("\nConnection request\n");
557
558 /* Linux supports a feature for acquiring the peer's credentials, but it
559 appears to be Linux-specific. This code is untested and unused, just
560 saved here for reference. */
561
562 /**********--------------------
563 struct ucred cr;
564 int cl=sizeof(cr);
565
566 if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl)==0) {
567 printf("Peer's pid=%d, uid=%d, gid=%d\n",
568 cr.pid, cr.uid, cr.gid);
569 --------------*****************/
570 }
571
572 if (dup_accept_socket < 0)
573 {
574 printf("Couldn't dup socket descriptor\n");
575 printf("421 Connection refused: %s\n", strerror(errno));
576 fprintf(out, "421 Connection refused: %s\r\n", strerror(errno));
577 fclose(out);
578 exit(2);
579 }
580
581 in = fdopen(dup_accept_socket, "r");
582
583 /* Loop for handling the conversation(s). For use in SMTP sessions, there are
584 default rules for determining input and output lines: the latter start with
585 digits. This means that the input looks like SMTP dialog. However, this
586 doesn't work for other tests (e.g. ident tests) so we have explicit '<' and
587 '>' flags for input and output as well as the defaults. */
588
589 for (; s != NULL; s = s->next)
590 {
591 char *ss = s->line;
592
593 /* Output lines either start with '>' or a digit. In the '>' case we can
594 fudge the sending of \r\n as required. Default is \r\n, ">>" send nothing,
595 ">CR>" sends \r only, and ">LF>" sends \n only. We can also force a
596 connection closedown by ">*eof". */
597
598 if (ss[0] == '>')
599 {
600 char *end = "\r\n";
7eb6c37c
JH
601 unsigned len = s->len;
602 printit(ss++, len--);
c55a77db
PH
603
604 if (strncmp(ss, "*eof", 4) == 0)
605 {
606 s = s->next;
607 goto END_OFF;
608 }
609
610 if (*ss == '>')
7eb6c37c 611 { end = ""; ss++; len--; }
c55a77db 612 else if (strncmp(ss, "CR>", 3) == 0)
7eb6c37c 613 { end = "\r"; ss += 3; len -= 3; }
c55a77db 614 else if (strncmp(ss, "LF>", 3) == 0)
7eb6c37c 615 { end = "\n"; ss += 3; len -= 3; }
c55a77db 616
7eb6c37c
JH
617 fwrite(ss, 1, len, out);
618 if (*end) fprintf(out, end);
c55a77db
PH
619 }
620
621 else if (isdigit((unsigned char)ss[0]))
622 {
623 printf("%s\n", ss);
624 fprintf(out, "%s\r\n", ss);
625 }
626
627 /* If the script line starts with "*sleep" we just sleep for a while
628 before continuing. */
629
630 else if (strncmp(ss, "*sleep ", 7) == 0)
631 {
632 int sleepfor = atoi(ss+7);
633 printf("%s\n", ss);
634 fflush(out);
635 sleep(sleepfor);
636 }
637
638 /* Otherwise the script line is the start of an input line we are expecting
639 from the client, or "*eof" indicating we expect the client to close the
640 connection. Read command line or data lines; the latter are indicated
641 by the expected line being just ".". If the line starts with '<', that
642 doesn't form part of the expected input. (This allows for incoming data
7eb6c37c
JH
643 starting with a digit.) If the line starts with '<<' we operate in
644 unbuffered rather than line mode and assume that a single read gets the
645 entire message. */
c55a77db
PH
646
647 else
648 {
649 int offset;
650 int data = strcmp(ss, ".") == 0;
651
7eb6c37c
JH
652 if (ss[0] != '<')
653 offset = 0;
654 else
c55a77db
PH
655 {
656 buffer[0] = '<';
7eb6c37c
JH
657 if (ss[1] != '<')
658 offset = 1;
659 else
660 {
661 buffer[1] = '<';
662 offset = 2;
663 }
c55a77db 664 }
c55a77db
PH
665
666 fflush(out);
667
7eb6c37c
JH
668 if (!linebuf)
669 {
670 int n;
671 char c;
672
673 alarm(timeout);
674 n = read(dup_accept_socket, CS buffer+offset, s->len - offset);
675 if (n == 0)
676 {
677 printf("%sxpected EOF read from client\n",
678 (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
679 s = s->next;
680 goto END_OFF;
681 }
682 if (offset != 2)
683 while (read(dup_accept_socket, &c, 1) == 1 && c != '\n') ;
684 alarm(0);
685 n += offset;
686
687 printit(buffer, n);
688
689 if (data) do
690 {
691 n = (read(dup_accept_socket, &c, 1) == 1 && c == '.');
692 while (c != '\n' && read(dup_accept_socket, &c, 1) == 1)
693 ;
694 } while (!n);
695 else if (memcmp(ss, buffer, n) != 0)
696 {
697 printf("Comparison failed - bailing out\nExpected: ");
698 printit(ss, n);
699 break;
700 }
701 }
702 else
703 {
704 for (;;)
705 {
706 int n;
707 alarm(timeout);
708 if (fgets(CS buffer+offset, sizeof(buffer)-offset, in) == NULL)
709 {
710 printf("%sxpected EOF read from client\n",
711 (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
712 s = s->next;
713 goto END_OFF;
714 }
715 alarm(0);
716 n = (int)strlen(CS buffer);
717 while (n > 0 && isspace(buffer[n-1])) n--;
718 buffer[n] = 0;
719 printf("%s\n", buffer);
720 if (!data || strcmp(CS buffer, ".") == 0) break;
721 }
722
723 if (strncmp(ss, CS buffer, (int)strlen(ss)) != 0)
724 {
725 printf("Comparison failed - bailing out\n");
726 printf("Expected: %s\n", ss);
727 break;
728 }
729 }
c55a77db
PH
730 }
731 }
732
733 END_OFF:
734 fclose(in);
735 fclose(out);
736 }
737
738if (s == NULL) printf("End of script\n");
739
740if (sockname != NULL) unlink(sockname);
741exit(0);
742}
743
744/* End of server.c */