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