Test case for retry_include_ip_address
[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;
60 char line[1];
61} line;
62
63
64/*************************************************
65* SIGALRM handler - crash out *
66*************************************************/
67
68static void
69sigalrm_handler(int sig)
70{
71sig = sig; /* Keep picky compilers happy */
72printf("\nServer timed out\n");
73exit(99);
74}
75
76
77/*************************************************
78* Get textual IP address *
79*************************************************/
80
81/* This function is copied from Exim */
82
83char *
84host_ntoa(const void *arg, char *buffer)
85{
86char *yield;
87
88/* The new world. It is annoying that we have to fish out the address from
89different places in the block, depending on what kind of address it is. It
90is also a pain that inet_ntop() returns a const char *, whereas the IPv4
91function inet_ntoa() returns just char *, and some picky compilers insist
92on warning if one assigns a const char * to a char *. Hence the casts. */
93
94#if HAVE_IPV6
95char addr_buffer[46];
96int family = ((struct sockaddr *)arg)->sa_family;
97if (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 }
103else
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
112if (strncmp(yield, "::ffff:", 7) == 0) yield += 7;
113
114#else /* HAVE_IPV6 */
115
116/* The old world */
117
118yield = inet_ntoa(((struct sockaddr_in *)arg)->sin_addr);
119#endif
120
121strcpy(buffer, yield);
122return 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
135int main(int argc, char **argv)
136{
137int i;
138int port = 0;
139int listen_socket[3] = { -1, -1, -1 };
140int accept_socket;
141int dup_accept_socket;
142int connection_count = 1;
143int count;
144int on = 1;
145int timeout = 5;
146int use_ipv4 = 1;
147int use_ipv6 = 1;
148int debug = 0;
149int na = 1;
150line *script = NULL;
151line *last = NULL;
152line *s;
153FILE *in, *out;
154
155char *sockname = NULL;
156unsigned char buffer[10240];
157
158struct sockaddr_un sockun; /* don't use "sun" */
159struct sockaddr_un sockun_accepted;
160int sockun_len = sizeof(sockun_accepted);
161
162#if HAVE_IPV6
163struct sockaddr_in6 sin6;
164struct sockaddr_in6 accepted;
165struct in6_addr anyaddr6 = IN6ADDR_ANY_INIT ;
166#else
167struct sockaddr_in accepted;
168#endif
169
170/* Always need an IPv4 structure */
171
172struct sockaddr_in sin4;
173
174int len = sizeof(accepted);
175
176
177/* Sort out the arguments */
178
179while (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
193if (!use_ipv4 && !use_ipv6)
194 {
195 printf("server: -noipv4 and -noipv6 cannot both be given\n");
196 exit(1);
197 }
198
199if (na >= argc)
200 {
201 printf("server: no port number or socket name given\n");
202 exit(1);
203 }
204
205if (argv[na][0] == '/')
206 {
207 sockname = argv[na];
208 unlink(sockname); /* in case left lying around */
209 }
210else port = atoi(argv[na]);
211na++;
212
213if (na < argc) connection_count = atoi(argv[na]);
214
215
216/* Create sockets */
217
218if (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 }
228else
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
269while a connection is being handled - this can happen as old connections lie
270around for a bit while crashed processes are tidied away. Without this, a
271connection will prevent reuse of the smtp port for listening. */
272
273for (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
286anyone can write to it. */
287
288if (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
309else
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
361error because it means that the IPv6 socket will handle IPv4 connections. Don't
362output anything, because it will mess up the test output, which will be
363different for systems that do this and those that don't. */
364
365for (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
379waiting for the first connection, read the standard input, which contains the
380script of things to do. A line containing "++++" is treated as end of file.
381This is so that the Perl driving script doesn't have to close the pipe -
382because that would cause it to wait for this process, which it doesn't yet want
383to do. The driving script adds the "++++" automatically - it doesn't actually
384appear in the test script. */
385
a719fce4 386while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
c55a77db
PH
387 {
388 line *next;
a719fce4 389 int n = (int)strlen(CS buffer);
c55a77db
PH
390 while (n > 0 && isspace(buffer[n-1])) n--;
391 buffer[n] = 0;
a719fce4 392 if (strcmp(CS buffer, "++++") == 0) break;
c55a77db
PH
393 next = malloc(sizeof(line) + n);
394 next->next = NULL;
a719fce4 395 strcpy(next->line, CS buffer);
c55a77db
PH
396 if (last == NULL) script = last = next;
397 else last->next = next;
398 last = next;
399 }
400
401fclose(stdin);
402
403/* SIGALRM handler crashes out */
404
405signal(SIGALRM, sigalrm_handler);
406
407/* s points to the current place in the script */
408
409s = script;
410
411for (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)
a719fce4 471 printf("\nConnection request from [%s]\n", host_ntoa(&accepted, CS buffer));
c55a77db
PH
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);
a719fce4 579 if (fgets(CS buffer+offset, sizeof(buffer)-offset, in) == NULL)
c55a77db
PH
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);
a719fce4 587 n = (int)strlen(CS buffer);
c55a77db
PH
588 while (n > 0 && isspace(buffer[n-1])) n--;
589 buffer[n] = 0;
590 printf("%s\n", buffer);
a719fce4 591 if (!data || strcmp(CS buffer, ".") == 0) break;
c55a77db
PH
592 }
593
a719fce4 594 if (strncmp(ss, CS buffer, (int)strlen(ss)) != 0)
c55a77db
PH
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
608if (s == NULL) printf("End of script\n");
609
610if (sockname != NULL) unlink(sockname);
611exit(0);
612}
613
614/* End of server.c */