SOCKS: as a client, talk SMTP via a socks5 proxy. Bug 1590
[exim.git] / test / src / server.c
CommitLineData
c55a77db
PH
1/* A little hacked up program that listens on a given port and allows a script
2to play the part of a remote MTA for testing purposes. This scripted version is
3hacked from my original interactive version. A further hack allows it to listen
4on a Unix domain socket as an alternative to a TCP/IP port.
5
6In an IPv6 world, listening happens on both an IPv6 and an IPv4 socket, always
7on 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
a719fce4 46# define HAVE_IPV6 1
c55a77db
PH
47#endif
48
49#ifndef S_ADDR_TYPE
a719fce4
JH
50# define S_ADDR_TYPE u_long
51#endif
52
53#ifndef CS
54# define CS (char *)
c55a77db
PH
55#endif
56
57
58typedef struct line {
59 struct line *next;
7eb6c37c 60 unsigned len;
c55a77db
PH
61 char line[1];
62} line;
63
64
65/*************************************************
66* SIGALRM handler - crash out *
67*************************************************/
68
69static void
70sigalrm_handler(int sig)
71{
72sig = sig; /* Keep picky compilers happy */
73printf("\nServer timed out\n");
74exit(99);
75}
76
77
78/*************************************************
79* Get textual IP address *
80*************************************************/
81
82/* This function is copied from Exim */
83
84char *
85host_ntoa(const void *arg, char *buffer)
86{
87char *yield;
88
89/* The new world. It is annoying that we have to fish out the address from
90different places in the block, depending on what kind of address it is. It
91is also a pain that inet_ntop() returns a const char *, whereas the IPv4
92function inet_ntoa() returns just char *, and some picky compilers insist
93on warning if one assigns a const char * to a char *. Hence the casts. */
94
95#if HAVE_IPV6
96char addr_buffer[46];
97int family = ((struct sockaddr *)arg)->sa_family;
98if (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 }
104else
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
113if (strncmp(yield, "::ffff:", 7) == 0) yield += 7;
114
115#else /* HAVE_IPV6 */
116
117/* The old world */
118
119yield = inet_ntoa(((struct sockaddr_in *)arg)->sin_addr);
120#endif
121
122strcpy(buffer, yield);
123return buffer;
124}
125
126
7eb6c37c
JH
127
128static void
129printit(char * s, int n)
130{
131while(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 }
141putchar('\n');
142}
143
144
145
c55a77db
PH
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
155int main(int argc, char **argv)
156{
157int i;
158int port = 0;
159int listen_socket[3] = { -1, -1, -1 };
160int accept_socket;
161int dup_accept_socket;
162int connection_count = 1;
163int count;
164int on = 1;
165int timeout = 5;
8a512ed5 166int initial_pause = 0;
c55a77db
PH
167int use_ipv4 = 1;
168int use_ipv6 = 1;
169int debug = 0;
170int na = 1;
171line *script = NULL;
172line *last = NULL;
173line *s;
174FILE *in, *out;
7eb6c37c 175int linebuf = 1;
c55a77db
PH
176
177char *sockname = NULL;
178unsigned char buffer[10240];
179
180struct sockaddr_un sockun; /* don't use "sun" */
181struct sockaddr_un sockun_accepted;
182int sockun_len = sizeof(sockun_accepted);
183
184#if HAVE_IPV6
185struct sockaddr_in6 sin6;
186struct sockaddr_in6 accepted;
187struct in6_addr anyaddr6 = IN6ADDR_ANY_INIT ;
188#else
189struct sockaddr_in accepted;
190#endif
191
192/* Always need an IPv4 structure */
193
194struct sockaddr_in sin4;
195
196int len = sizeof(accepted);
197
198
199/* Sort out the arguments */
200
201while (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]);
8a512ed5 205 else if (strcmp(argv[na], "-i") == 0) initial_pause = atoi(argv[++na]);
c55a77db
PH
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
216if (!use_ipv4 && !use_ipv6)
217 {
218 printf("server: -noipv4 and -noipv6 cannot both be given\n");
219 exit(1);
220 }
221
222if (na >= argc)
223 {
224 printf("server: no port number or socket name given\n");
225 exit(1);
226 }
227
228if (argv[na][0] == '/')
229 {
230 sockname = argv[na];
231 unlink(sockname); /* in case left lying around */
232 }
233else port = atoi(argv[na]);
234na++;
235
236if (na < argc) connection_count = atoi(argv[na]);
237
238
8a512ed5
JH
239/* Initial pause (before creating listen sockets */
240if (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
c55a77db
PH
250/* Create sockets */
251
252if (port == 0) /* Unix domain */
253 {
8a512ed5 254 if (debug) printf("%d: Creating Unix domain socket\n", time(NULL));
c55a77db
PH
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 }
262else
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
303while a connection is being handled - this can happen as old connections lie
304around for a bit while crashed processes are tidied away. Without this, a
305connection will prevent reuse of the smtp port for listening. */
306
307for (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
320anyone can write to it. */
321
322if (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
343else
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
395error because it means that the IPv6 socket will handle IPv4 connections. Don't
396output anything, because it will mess up the test output, which will be
397different for systems that do this and those that don't. */
398
399for (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
413waiting for the first connection, read the standard input, which contains the
414script of things to do. A line containing "++++" is treated as end of file.
415This is so that the Perl driving script doesn't have to close the pipe -
416because that would cause it to wait for this process, which it doesn't yet want
417to do. The driving script adds the "++++" automatically - it doesn't actually
7eb6c37c 418appear in the test script. Within lines we interpret \xNN and \\ groups */
c55a77db 419
a719fce4 420while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
c55a77db
PH
421 {
422 line *next;
7eb6c37c 423 char * d;
a719fce4 424 int n = (int)strlen(CS buffer);
7eb6c37c
JH
425
426 if (n > 1 && buffer[0] == '>' && buffer[1] == '>')
427 linebuf = 0;
c55a77db
PH
428 while (n > 0 && isspace(buffer[n-1])) n--;
429 buffer[n] = 0;
a719fce4 430 if (strcmp(CS buffer, "++++") == 0) break;
c55a77db
PH
431 next = malloc(sizeof(line) + n);
432 next->next = NULL;
7eb6c37c
JH
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;
c55a77db
PH
451 if (last == NULL) script = last = next;
452 else last->next = next;
453 last = next;
454 }
455
456fclose(stdin);
457
458/* SIGALRM handler crashes out */
459
460signal(SIGALRM, sigalrm_handler);
461
462/* s points to the current place in the script */
463
464s = script;
465
466for (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)
a719fce4 526 printf("\nConnection request from [%s]\n", host_ntoa(&accepted, CS buffer));
c55a77db
PH
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";
7eb6c37c
JH
574 unsigned len = s->len;
575 printit(ss++, len--);
c55a77db
PH
576
577 if (strncmp(ss, "*eof", 4) == 0)
578 {
579 s = s->next;
580 goto END_OFF;
581 }
582
583 if (*ss == '>')
7eb6c37c 584 { end = ""; ss++; len--; }
c55a77db 585 else if (strncmp(ss, "CR>", 3) == 0)
7eb6c37c 586 { end = "\r"; ss += 3; len -= 3; }
c55a77db 587 else if (strncmp(ss, "LF>", 3) == 0)
7eb6c37c 588 { end = "\n"; ss += 3; len -= 3; }
c55a77db 589
7eb6c37c
JH
590 fwrite(ss, 1, len, out);
591 if (*end) fprintf(out, end);
c55a77db
PH
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
7eb6c37c
JH
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. */
c55a77db
PH
619
620 else
621 {
622 int offset;
623 int data = strcmp(ss, ".") == 0;
624
7eb6c37c
JH
625 if (ss[0] != '<')
626 offset = 0;
627 else
c55a77db
PH
628 {
629 buffer[0] = '<';
7eb6c37c
JH
630 if (ss[1] != '<')
631 offset = 1;
632 else
633 {
634 buffer[1] = '<';
635 offset = 2;
636 }
c55a77db 637 }
c55a77db
PH
638
639 fflush(out);
640
7eb6c37c
JH
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 }
c55a77db
PH
703 }
704 }
705
706 END_OFF:
707 fclose(in);
708 fclose(out);
709 }
710
711if (s == NULL) printf("End of script\n");
712
713if (sockname != NULL) unlink(sockname);
714exit(0);
715}
716
717/* End of server.c */