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