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