Support SOA lookup in dnsdb lookups. Bug 286
[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
202 while (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]);
206   else if (strcmp(argv[na], "-i") == 0) initial_pause = atoi(argv[++na]);
207   else if (strcmp(argv[na], "-noipv4") == 0) use_ipv4 = 0;
208   else if (strcmp(argv[na], "-noipv6") == 0) use_ipv6 = 0;
209   else if (strcmp(argv[na], "-oP") == 0) pidfile = argv[++na];
210   else
211     {
212     printf("server: unknown option %s\n", argv[na]);
213     exit(1);
214     }
215   na++;
216   }
217
218 if (!use_ipv4 && !use_ipv6)
219   {
220   printf("server: -noipv4 and -noipv6 cannot both be given\n");
221   exit(1);
222   }
223
224 if (na >= argc)
225   {
226   printf("server: no port number or socket name given\n");
227   exit(1);
228   }
229
230 if (argv[na][0] == '/')
231   {
232   sockname = argv[na];
233   unlink(sockname);       /* in case left lying around */
234   }
235 else port = atoi(argv[na]);
236 na++;
237
238 if (na < argc) connection_count = atoi(argv[na]);
239
240
241 /* Initial pause (before creating listen sockets */
242 if (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
252 /* Create sockets */
253
254 if (port == 0)  /* Unix domain */
255   {
256   if (debug) printf("%d: Creating Unix domain socket\n", time(NULL));
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   }
264 else
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
305 while a connection is being handled - this can happen as old connections lie
306 around for a bit while crashed processes are tidied away.  Without this, a
307 connection will prevent reuse of the smtp port for listening. */
308
309 for (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
322 anyone can write to it. */
323
324 if (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
345 else
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
397 error because it means that the IPv6 socket will handle IPv4 connections. Don't
398 output anything, because it will mess up the test output, which will be
399 different for systems that do this and those that don't. */
400
401 for (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
414 if (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
426 /* This program handles only a fixed number of connections, in sequence. Before
427 waiting for the first connection, read the standard input, which contains the
428 script of things to do. A line containing "++++" is treated as end of file.
429 This is so that the Perl driving script doesn't have to close the pipe -
430 because that would cause it to wait for this process, which it doesn't yet want
431 to do. The driving script adds the "++++" automatically - it doesn't actually
432 appear in the test script. Within lines we interpret \xNN and \\ groups */
433
434 while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
435   {
436   line *next;
437   char * d;
438   int n = (int)strlen(CS buffer);
439
440   if (n > 1 && buffer[0] == '>' && buffer[1] == '>')
441     linebuf = 0;
442   while (n > 0 && isspace(buffer[n-1])) n--;
443   buffer[n] = 0;
444   if (strcmp(CS buffer, "++++") == 0) break;
445   next = malloc(sizeof(line) + n);
446   next->next = NULL;
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;
465   if (last == NULL) script = last = next;
466     else last->next = next;
467   last = next;
468   }
469
470 fclose(stdin);
471
472 /* SIGALRM handler crashes out */
473
474 signal(SIGALRM, sigalrm_handler);
475
476 /* s points to the current place in the script */
477
478 s = script;
479
480 for (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)
540     printf("\nConnection request from [%s]\n", host_ntoa(&accepted, CS buffer));
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";
588       unsigned len = s->len;
589       printit(ss++, len--);
590
591       if (strncmp(ss, "*eof", 4) == 0)
592         {
593         s = s->next;
594         goto END_OFF;
595         }
596
597       if (*ss == '>')
598         { end = ""; ss++; len--; }
599       else if (strncmp(ss, "CR>", 3) == 0)
600         { end = "\r"; ss += 3; len -= 3; }
601       else if (strncmp(ss, "LF>", 3) == 0)
602         { end = "\n"; ss += 3; len -= 3; }
603
604       fwrite(ss, 1, len, out);
605       if (*end) fprintf(out, end);
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
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. */
633
634     else
635       {
636       int offset;
637       int data = strcmp(ss, ".") == 0;
638
639       if (ss[0] != '<')
640         offset = 0;
641       else
642         {
643         buffer[0] = '<';
644         if (ss[1] != '<')
645           offset = 1;
646         else
647           {
648           buffer[1] = '<';
649           offset = 2;
650           }
651         }
652
653       fflush(out);
654
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         }
717       }
718     }
719
720   END_OFF:
721   fclose(in);
722   fclose(out);
723   }
724
725 if (s == NULL) printf("End of script\n");
726
727 if (sockname != NULL) unlink(sockname);
728 exit(0);
729 }
730
731 /* End of server.c */