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