Additional tests - didn't show any problems.
[exim.git] / test / src / server.c
CommitLineData
c55a77db
PH
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
4to play the part of a remote MTA for testing purposes. This scripted version is
5hacked from my original interactive version. A further hack allows it to listen
6on a Unix domain socket as an alternative to a TCP/IP port.
7
8In an IPv6 world, listening happens on both an IPv6 and an IPv4 socket, always
9on 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
56typedef struct line {
57 struct line *next;
58 char line[1];
59} line;
60
61
62/*************************************************
63* SIGALRM handler - crash out *
64*************************************************/
65
66static void
67sigalrm_handler(int sig)
68{
69sig = sig; /* Keep picky compilers happy */
70printf("\nServer timed out\n");
71exit(99);
72}
73
74
75/*************************************************
76* Get textual IP address *
77*************************************************/
78
79/* This function is copied from Exim */
80
81char *
82host_ntoa(const void *arg, char *buffer)
83{
84char *yield;
85
86/* The new world. It is annoying that we have to fish out the address from
87different places in the block, depending on what kind of address it is. It
88is also a pain that inet_ntop() returns a const char *, whereas the IPv4
89function inet_ntoa() returns just char *, and some picky compilers insist
90on warning if one assigns a const char * to a char *. Hence the casts. */
91
92#if HAVE_IPV6
93char addr_buffer[46];
94int family = ((struct sockaddr *)arg)->sa_family;
95if (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 }
101else
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
110if (strncmp(yield, "::ffff:", 7) == 0) yield += 7;
111
112#else /* HAVE_IPV6 */
113
114/* The old world */
115
116yield = inet_ntoa(((struct sockaddr_in *)arg)->sin_addr);
117#endif
118
119strcpy(buffer, yield);
120return 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
133int main(int argc, char **argv)
134{
135int i;
136int port = 0;
137int listen_socket[3] = { -1, -1, -1 };
138int accept_socket;
139int dup_accept_socket;
140int connection_count = 1;
141int count;
142int on = 1;
143int timeout = 5;
144int use_ipv4 = 1;
145int use_ipv6 = 1;
146int debug = 0;
147int na = 1;
148line *script = NULL;
149line *last = NULL;
150line *s;
151FILE *in, *out;
152
153char *sockname = NULL;
154unsigned char buffer[10240];
155
156struct sockaddr_un sockun; /* don't use "sun" */
157struct sockaddr_un sockun_accepted;
158int sockun_len = sizeof(sockun_accepted);
159
160#if HAVE_IPV6
161struct sockaddr_in6 sin6;
162struct sockaddr_in6 accepted;
163struct in6_addr anyaddr6 = IN6ADDR_ANY_INIT ;
164#else
165struct sockaddr_in accepted;
166#endif
167
168/* Always need an IPv4 structure */
169
170struct sockaddr_in sin4;
171
172int len = sizeof(accepted);
173
174
175/* Sort out the arguments */
176
177while (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
191if (!use_ipv4 && !use_ipv6)
192 {
193 printf("server: -noipv4 and -noipv6 cannot both be given\n");
194 exit(1);
195 }
196
197if (na >= argc)
198 {
199 printf("server: no port number or socket name given\n");
200 exit(1);
201 }
202
203if (argv[na][0] == '/')
204 {
205 sockname = argv[na];
206 unlink(sockname); /* in case left lying around */
207 }
208else port = atoi(argv[na]);
209na++;
210
211if (na < argc) connection_count = atoi(argv[na]);
212
213
214/* Create sockets */
215
216if (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 }
226else
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
267while a connection is being handled - this can happen as old connections lie
268around for a bit while crashed processes are tidied away. Without this, a
269connection will prevent reuse of the smtp port for listening. */
270
271for (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
284anyone can write to it. */
285
286if (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
307else
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
359error because it means that the IPv6 socket will handle IPv4 connections. Don't
360output anything, because it will mess up the test output, which will be
361different for systems that do this and those that don't. */
362
363for (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
377waiting for the first connection, read the standard input, which contains the
378script of things to do. A line containing "++++" is treated as end of file.
379This is so that the Perl driving script doesn't have to close the pipe -
380because that would cause it to wait for this process, which it doesn't yet want
381to do. The driving script adds the "++++" automatically - it doesn't actually
382appear in the test script. */
383
384while (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
399fclose(stdin);
400
401/* SIGALRM handler crashes out */
402
403signal(SIGALRM, sigalrm_handler);
404
405/* s points to the current place in the script */
406
407s = script;
408
409for (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
606if (s == NULL) printf("End of script\n");
607
608if (sockname != NULL) unlink(sockname);
609exit(0);
610}
611
612/* End of server.c */