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