Fix post-transport-crash.
[users/jgh/exim.git] / test / src / server.c
1 /* A little hacked up program that listens on a given port and allows a script
2 to play the part of a remote MTA for testing purposes. This scripted version is
3 hacked from my original interactive version. A further hack allows it to listen
4 on a Unix domain socket as an alternative to a TCP/IP port.
5
6 In an IPv6 world, listening happens on both an IPv6 and an IPv4 socket, always
7 on 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 #ifndef CS
54 # define CS (char *)
55 #endif
56
57
58 typedef struct line {
59   struct line *next;
60   unsigned len;
61   char line[1];
62 } line;
63
64
65 /*************************************************
66 *            SIGALRM handler - crash out         *
67 *************************************************/
68
69 static void
70 sigalrm_handler(int sig)
71 {
72 sig = sig;    /* Keep picky compilers happy */
73 printf("\nServer timed out\n");
74 exit(99);
75 }
76
77
78 /*************************************************
79 *          Get textual IP address                *
80 *************************************************/
81
82 /* This function is copied from Exim */
83
84 char *
85 host_ntoa(const void *arg, char *buffer)
86 {
87 char *yield;
88
89 /* The new world. It is annoying that we have to fish out the address from
90 different places in the block, depending on what kind of address it is. It
91 is also a pain that inet_ntop() returns a const char *, whereas the IPv4
92 function inet_ntoa() returns just char *, and some picky compilers insist
93 on warning if one assigns a const char * to a char *. Hence the casts. */
94
95 #if HAVE_IPV6
96 char addr_buffer[46];
97 int family = ((struct sockaddr *)arg)->sa_family;
98 if (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   }
104 else
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
113 if (strncmp(yield, "::ffff:", 7) == 0) yield += 7;
114
115 #else /* HAVE_IPV6 */
116
117 /* The old world */
118
119 yield = inet_ntoa(((struct sockaddr_in *)arg)->sin_addr);
120 #endif
121
122 strcpy(buffer, yield);
123 return buffer;
124 }
125
126
127
128 static void
129 printit(char * s, int n)
130 {
131 while(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   }
141 putchar('\n');
142 }
143
144
145
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
155 int main(int argc, char **argv)
156 {
157 int i;
158 int port = 0;
159 int listen_socket[3] = { -1, -1, -1 };
160 int accept_socket;
161 int dup_accept_socket;
162 int connection_count = 1;
163 int count;
164 int on = 1;
165 int timeout = 5;
166 int initial_pause = 0;
167 int use_ipv4 = 1;
168 int use_ipv6 = 1;
169 int debug = 0;
170 int na = 1;
171 line *script = NULL;
172 line *last = NULL;
173 line *s;
174 FILE *in, *out;
175 int linebuf = 1;
176 char *pidfile = NULL;
177
178 char *sockname = NULL;
179 unsigned char buffer[10240];
180
181 struct sockaddr_un sockun;            /* don't use "sun" */
182 struct sockaddr_un sockun_accepted;
183 int sockun_len = sizeof(sockun_accepted);
184
185 #if HAVE_IPV6
186 struct sockaddr_in6 sin6;
187 struct sockaddr_in6 accepted;
188 struct in6_addr anyaddr6 =  IN6ADDR_ANY_INIT ;
189 #else
190 struct sockaddr_in accepted;
191 #endif
192
193 /* Always need an IPv4 structure */
194
195 struct sockaddr_in sin4;
196
197 int len = sizeof(accepted);
198
199
200 /* Sort out the arguments */
201 if (argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")))
202   {
203   printf("Usage: %s [options]\n", argv[0]);
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   }
214
215 while (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]);
219   else if (strcmp(argv[na], "-i") == 0) initial_pause = atoi(argv[++na]);
220   else if (strcmp(argv[na], "-noipv4") == 0) use_ipv4 = 0;
221   else if (strcmp(argv[na], "-noipv6") == 0) use_ipv6 = 0;
222   else if (strcmp(argv[na], "-oP") == 0) pidfile = argv[++na];
223   else
224     {
225     printf("server: unknown option %s, try -h or --help\n", argv[na]);
226     exit(1);
227     }
228   na++;
229   }
230
231 if (!use_ipv4 && !use_ipv6)
232   {
233   printf("server: -noipv4 and -noipv6 cannot both be given\n");
234   exit(1);
235   }
236
237 if (na >= argc)
238   {
239   printf("server: no port number or socket name given\n");
240   exit(1);
241   }
242
243 if (argv[na][0] == '/')
244   {
245   sockname = argv[na];
246   unlink(sockname);       /* in case left lying around */
247   }
248 else port = atoi(argv[na]);
249 na++;
250
251 if (na < argc) connection_count = atoi(argv[na]);
252
253
254 /* Initial pause (before creating listen sockets */
255 if (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
265 /* Create sockets */
266
267 if (port == 0)  /* Unix domain */
268   {
269   if (debug) printf("%d: Creating Unix domain socket\n", time(NULL));
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   }
277 else
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
318 while a connection is being handled - this can happen as old connections lie
319 around for a bit while crashed processes are tidied away.  Without this, a
320 connection will prevent reuse of the smtp port for listening. */
321
322 for (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
335 anyone can write to it. */
336
337 if (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
358 else
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
410 error because it means that the IPv6 socket will handle IPv4 connections. Don't
411 output anything, because it will mess up the test output, which will be
412 different for systems that do this and those that don't. */
413
414 for (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
427 if (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
439 /* This program handles only a fixed number of connections, in sequence. Before
440 waiting for the first connection, read the standard input, which contains the
441 script of things to do. A line containing "++++" is treated as end of file.
442 This is so that the Perl driving script doesn't have to close the pipe -
443 because that would cause it to wait for this process, which it doesn't yet want
444 to do. The driving script adds the "++++" automatically - it doesn't actually
445 appear in the test script. Within lines we interpret \xNN and \\ groups */
446
447 while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
448   {
449   line *next;
450   char * d;
451   int n = (int)strlen(CS buffer);
452
453   if (n > 1 && buffer[0] == '>' && buffer[1] == '>')
454     linebuf = 0;
455   while (n > 0 && isspace(buffer[n-1])) n--;
456   buffer[n] = 0;
457   if (strcmp(CS buffer, "++++") == 0) break;
458   next = malloc(sizeof(line) + n);
459   next->next = NULL;
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;
478   if (last == NULL) script = last = next;
479     else last->next = next;
480   last = next;
481   }
482
483 fclose(stdin);
484
485 /* SIGALRM handler crashes out */
486
487 signal(SIGALRM, sigalrm_handler);
488
489 /* s points to the current place in the script */
490
491 s = script;
492
493 for (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)
553     printf("\nConnection request from [%s]\n", host_ntoa(&accepted, CS buffer));
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";
601       unsigned len = s->len;
602       printit(ss++, len--);
603
604       if (strncmp(ss, "*eof", 4) == 0)
605         {
606         s = s->next;
607         goto END_OFF;
608         }
609
610       if (*ss == '>')
611         { end = ""; ss++; len--; }
612       else if (strncmp(ss, "CR>", 3) == 0)
613         { end = "\r"; ss += 3; len -= 3; }
614       else if (strncmp(ss, "LF>", 3) == 0)
615         { end = "\n"; ss += 3; len -= 3; }
616
617       fwrite(ss, 1, len, out);
618       if (*end) fprintf(out, end);
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
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. */
646
647     else
648       {
649       int offset;
650       int data = strcmp(ss, ".") == 0;
651
652       if (ss[0] != '<')
653         offset = 0;
654       else
655         {
656         buffer[0] = '<';
657         if (ss[1] != '<')
658           offset = 1;
659         else
660           {
661           buffer[1] = '<';
662           offset = 2;
663           }
664         }
665
666       fflush(out);
667
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         }
730       }
731     }
732
733   END_OFF:
734   fclose(in);
735   fclose(out);
736   }
737
738 if (s == NULL) printf("End of script\n");
739
740 if (sockname != NULL) unlink(sockname);
741 exit(0);
742 }
743
744 /* End of server.c */