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.
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. */
9 /* ANSI C standard includes */
24 #include <sys/types.h>
26 #include <netinet/in_systm.h>
27 #include <netinet/in.h>
28 #include <netinet/ip.h>
29 #include <netinet/tcp.h>
31 #ifdef HAVE_NETINET_IP_VAR_H
32 # include <netinet/ip_var.h>
36 #include <arpa/inet.h>
38 #include <sys/resource.h>
39 #include <sys/socket.h>
51 # define S_ADDR_TYPE u_long
56 # define CCS (const char *)
66 typedef unsigned BOOL;
71 /*************************************************
72 * SIGALRM handler - crash out *
73 *************************************************/
77 sigalrm_handler(int sig)
79 sig = sig; /* Keep picky compilers happy */
80 printf("\nServer timed out\n");
81 exit(tmo_noerror ? 0 : 99);
85 /*************************************************
86 * Get textual IP address *
87 *************************************************/
89 /* This function is copied from Exim */
92 host_ntoa(const void *arg, char *buffer)
96 /* The new world. It is annoying that we have to fish out the address from
97 different places in the block, depending on what kind of address it is. It
98 is also a pain that inet_ntop() returns a const char *, whereas the IPv4
99 function inet_ntoa() returns just char *, and some picky compilers insist
100 on warning if one assigns a const char * to a char *. Hence the casts. */
103 char addr_buffer[46];
104 int family = ((struct sockaddr *)arg)->sa_family;
105 if (family == AF_INET6)
107 struct sockaddr_in6 *sk = (struct sockaddr_in6 *)arg;
108 yield = (char *)inet_ntop(family, &(sk->sin6_addr), addr_buffer,
109 sizeof(addr_buffer));
113 struct sockaddr_in *sk = (struct sockaddr_in *)arg;
114 yield = (char *)inet_ntop(family, &(sk->sin_addr), addr_buffer,
115 sizeof(addr_buffer));
118 /* If the result is a mapped IPv4 address, show it in V4 format. */
120 if (strncmp(yield, "::ffff:", 7) == 0) yield += 7;
122 #else /* HAVE_IPV6 */
126 yield = inet_ntoa(((struct sockaddr_in *)arg)->sin_addr);
129 strcpy(buffer, yield);
136 printit(char * s, int n)
140 unsigned char c = *s++;
143 else if (c >= ' ' && c <= '~') /* assumes ascii */
146 printf("\\x%02x", c);
153 /*************************************************
155 *************************************************/
157 #define v6n 0 /* IPv6 socket number */
158 #define v4n 1 /* IPv4 socket number */
159 #define udn 2 /* Unix domain socket number */
160 #define skn 2 /* Potential number of sockets */
163 main(int argc, char **argv)
167 int listen_socket[3] = { -1, -1, -1 };
169 int dup_accept_socket;
170 int connection_count = 1;
174 int initial_pause = 0, tfo = 0;
184 char *pidfile = NULL;
186 char *sockname = NULL;
187 unsigned char buffer[10240];
189 struct sockaddr_un sockun; /* don't use "sun" */
190 struct sockaddr_un sockun_accepted;
191 int sockun_len = sizeof(sockun_accepted);
194 struct sockaddr_in6 sin6;
195 struct sockaddr_in6 accepted;
196 struct in6_addr anyaddr6 = IN6ADDR_ANY_INIT ;
198 struct sockaddr_in accepted;
201 /* Always need an IPv4 structure */
203 struct sockaddr_in sin4;
205 int len = sizeof(accepted);
208 /* Sort out the arguments */
209 if (argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")))
211 printf("Usage: %s [options] port|socket [connection count]\n", argv[0]);
214 "\n\t-i n n seconds initial delay"
215 "\n\t-noipv4 disable ipv4"
216 "\n\t-noipv6 disable ipv6"
217 "\n\t-oP file write PID to file"
218 "\n\t-t n n seconds timeout"
219 "\n\t-tfo enable TCP Fast Open"
224 while (na < argc && argv[na][0] == '-')
226 if (strcmp(argv[na], "-d") == 0) debug = 1;
227 else if (strcmp(argv[na], "-tfo") == 0) tfo = 1;
228 else if (strcmp(argv[na], "-t") == 0)
230 if (tmo_noerror = ((timeout = atoi(argv[++na])) < 0)) timeout = -timeout;
232 else if (strcmp(argv[na], "-i") == 0) initial_pause = atoi(argv[++na]);
233 else if (strcmp(argv[na], "-noipv4") == 0) use_ipv4 = 0;
234 else if (strcmp(argv[na], "-noipv6") == 0) use_ipv6 = 0;
235 else if (strcmp(argv[na], "-oP") == 0) pidfile = argv[++na];
238 printf("server: unknown option %s, try -h or --help\n", argv[na]);
244 if (!use_ipv4 && !use_ipv6)
246 printf("server: -noipv4 and -noipv6 cannot both be given\n");
252 printf("server: no port number or socket name given\n");
256 if (argv[na][0] == '/')
259 unlink(sockname); /* in case left lying around */
261 else port = atoi(argv[na]);
264 if (na < argc) connection_count = atoi(argv[na]);
267 /* Initial pause (before creating listen sockets */
268 if (initial_pause > 0)
271 printf("%ld: Inital pause of %d seconds\n", (long)time(NULL), initial_pause);
273 printf("Inital pause of %d seconds\n", initial_pause);
274 while (initial_pause > 0)
275 initial_pause = sleep(initial_pause);
280 if (port == 0) /* Unix domain */
282 if (debug) printf("%l: Creating Unix domain socket\n", (long) time(NULL));
283 listen_socket[udn] = socket(PF_UNIX, SOCK_STREAM, 0);
284 if (listen_socket[udn] < 0)
286 printf("Unix domain socket creation failed: %s\n", strerror(errno));
295 if (debug) printf("Creating IPv6 socket\n");
296 listen_socket[v6n] = socket(AF_INET6, SOCK_STREAM, 0);
297 if (listen_socket[v6n] < 0)
299 printf("IPv6 socket creation failed: %s\n", strerror(errno));
302 #if defined(TCP_FASTOPEN) && !defined(__APPLE__)
306 if (setsockopt(listen_socket[v6n], IPPROTO_TCP, TCP_FASTOPEN,
307 &backlog, sizeof(backlog)))
308 if (debug) printf("setsockopt TCP_FASTOPEN: %s\n", strerror(errno));
311 /* If this is an IPv6 wildcard socket, set IPV6_V6ONLY if that option is
315 if (setsockopt(listen_socket[v6n], IPPROTO_IPV6, IPV6_V6ONLY, (char *)(&on),
317 printf("Setting IPV6_V6ONLY on IPv6 wildcard "
318 "socket failed (%s): carrying on without it\n", strerror(errno));
319 #endif /* IPV6_V6ONLY */
321 #endif /* HAVE_IPV6 */
323 /* Create an IPv4 socket if required */
327 if (debug) printf("Creating IPv4 socket\n");
328 listen_socket[v4n] = socket(AF_INET, SOCK_STREAM, 0);
329 if (listen_socket[v4n] < 0)
331 printf("IPv4 socket creation failed: %s\n", strerror(errno));
334 #if defined(TCP_FASTOPEN) && !defined(__APPLE__)
338 if (setsockopt(listen_socket[v4n], IPPROTO_TCP, TCP_FASTOPEN,
339 &backlog, sizeof(backlog)))
340 if (debug) printf("setsockopt TCP_FASTOPEN: %s\n", strerror(errno));
347 /* Set SO_REUSEADDR on the IP sockets so that the program can be restarted
348 while a connection is being handled - this can happen as old connections lie
349 around for a bit while crashed processes are tidied away. Without this, a
350 connection will prevent reuse of the smtp port for listening. */
352 for (i = v6n; i <= v4n; i++)
354 if (listen_socket[i] >= 0 &&
355 setsockopt(listen_socket[i], SOL_SOCKET, SO_REUSEADDR, (char *)(&on),
358 printf("setting SO_REUSEADDR on socket failed: %s\n", strerror(errno));
364 /* Now bind the sockets to the required port or path. If a path, ensure
365 anyone can write to it. */
370 sockun.sun_family = AF_UNIX;
371 if (debug) printf("Binding Unix domain socket\n");
372 sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1), sockname);
373 if (bind(listen_socket[udn], (struct sockaddr *)&sockun, sizeof(sockun)) < 0)
375 printf("Unix domain socket bind() failed: %s\n", strerror(errno));
378 (void)stat(sockname, &statbuf);
379 if (debug) printf("Setting Unix domain socket mode: %0x\n",
380 statbuf.st_mode | 0777);
381 if (chmod(sockname, statbuf.st_mode | 0777) < 0)
383 printf("Unix domain socket chmod() failed: %s\n", strerror(errno));
390 for (i = 0; i < skn; i++)
392 if (listen_socket[i] < 0) continue;
394 /* For an IPv6 listen, use an IPv6 socket */
399 memset(&sin6, 0, sizeof(sin6));
400 sin6.sin6_family = AF_INET6;
401 sin6.sin6_port = htons(port);
402 sin6.sin6_addr = anyaddr6;
403 if (bind(listen_socket[i], (struct sockaddr *)&sin6, sizeof(sin6)) < 0)
405 printf("IPv6 socket bind(port %d) failed: %s\n", port, strerror(errno));
412 /* For an IPv4 bind, use an IPv4 socket, even in an IPv6 world. If an IPv4
413 bind fails EADDRINUSE after IPv6 success, carry on, because it means the
414 IPv6 socket will handle IPv4 connections. */
417 memset(&sin4, 0, sizeof(sin4));
418 sin4.sin_family = AF_INET;
419 sin4.sin_addr.s_addr = (S_ADDR_TYPE)INADDR_ANY;
420 sin4.sin_port = htons(port);
421 if (bind(listen_socket[i], (struct sockaddr *)&sin4, sizeof(sin4)) < 0)
422 if (listen_socket[v6n] < 0 || errno != EADDRINUSE)
424 printf("IPv4 socket bind(port %d) failed: %s\n", port, strerror(errno));
429 close(listen_socket[i]);
430 listen_socket[i] = -1;
437 /* Start listening. If IPv4 fails EADDRINUSE after IPv6 succeeds, ignore the
438 error because it means that the IPv6 socket will handle IPv4 connections. Don't
439 output anything, because it will mess up the test output, which will be
440 different for systems that do this and those that don't. */
442 for (i = 0; i <= skn; i++) if (listen_socket[i] >= 0)
444 if (listen(listen_socket[i], 5) < 0)
445 if (i != v4n || listen_socket[v6n] < 0 || errno != EADDRINUSE)
447 printf("listen() failed: %s\n", strerror(errno));
451 #if defined(TCP_FASTOPEN) && defined(__APPLE__)
453 && setsockopt(listen_socket[v4n], IPPROTO_TCP, TCP_FASTOPEN, &on, sizeof(on))
455 printf("setsockopt TCP_FASTOPEN: %s\n", strerror(errno));
463 if (!(p = fopen(pidfile, "w")))
465 fprintf(stderr, "pidfile create failed: %s\n", strerror(errno));
468 fprintf(p, "%ld\n", (long)getpid());
472 /* This program handles only a fixed number of connections, in sequence. Before
473 waiting for the first connection, read the standard input, which contains the
474 script of things to do. A line containing "++++" is treated as end of file.
475 This is so that the Perl driving script doesn't have to close the pipe -
476 because that would cause it to wait for this process, which it doesn't yet want
477 to do. The driving script adds the "++++" automatically - it doesn't actually
478 appear in the test script. Within lines we interpret \xNN and \\ groups */
480 while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
484 int n = (int)strlen(CS buffer);
486 if (n > 1 && buffer[0] == '>' && buffer[1] == '>')
488 while (n > 0 && isspace(buffer[n-1])) n--;
490 if (strcmp(CS buffer, "++++") == 0) break;
491 next = malloc(sizeof(line) + n);
495 char * s = CS buffer;
500 if (cl == '\\' && (cl = *++s) == 'x')
502 if ((ch = *++s - '0') > 9 && (ch -= 'A'-'9'-1) > 15) ch -= 'a'-'A';
503 if ((cl = *++s - '0') > 9 && (cl -= 'A'-'9'-1) > 15) cl -= 'a'-'A';
510 next->len = d - next->line - 1;
511 if (last == NULL) script = last = next;
512 else last->next = next;
518 /* SIGALRM handler crashes out */
520 signal(SIGALRM, sigalrm_handler);
522 /* s points to the current place in the script */
526 for (count = 0; count < connection_count; count++)
531 } content_length = { 0, FALSE };
536 printf("Listening on %s ... ", sockname);
538 accept_socket = accept(listen_socket[udn],
539 (struct sockaddr *)&sockun_accepted, &sockun_len);
546 fd_set select_listen;
548 printf("Listening on port %d ... ", port);
551 FD_ZERO(&select_listen);
552 for (i = 0; i < skn; i++)
554 if (listen_socket[i] >= 0) FD_SET(listen_socket[i], &select_listen);
555 if (listen_socket[i] > max_socket) max_socket = listen_socket[i];
558 if ((lcount = select(max_socket + 1, &select_listen, NULL, NULL, NULL)) < 0)
560 printf("Select failed\n");
566 for (i = 0; i < skn; i++)
567 if (listen_socket[i] > 0 && FD_ISSET(listen_socket[i], &select_listen))
569 accept_socket = accept(listen_socket[i],
570 (struct sockaddr *)&accepted, &len);
571 FD_CLR(listen_socket[i], &select_listen);
577 if (accept_socket < 0)
579 printf("accept() failed: %s\n", strerror(errno));
583 out = fdopen(accept_socket, "w");
585 dup_accept_socket = dup(accept_socket);
588 printf("\nConnection request from [%s]\n", host_ntoa(&accepted, CS buffer));
591 printf("\nConnection request\n");
593 /* Linux supports a feature for acquiring the peer's credentials, but it
594 appears to be Linux-specific. This code is untested and unused, just
595 saved here for reference. */
597 /**********--------------------
601 if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl)==0) {
602 printf("Peer's pid=%d, uid=%d, gid=%d\n",
603 cr.pid, cr.uid, cr.gid);
604 --------------*****************/
608 if (dup_accept_socket < 0)
610 printf("Couldn't dup socket descriptor\n");
611 printf("421 Connection refused: %s\n", strerror(errno));
612 fprintf(out, "421 Connection refused: %s\r\n", strerror(errno));
617 in = fdopen(dup_accept_socket, "r");
619 /* Loop for handling the conversation(s). For use in SMTP sessions, there are
620 default rules for determining input and output lines: the latter start with
621 digits. This means that the input looks like SMTP dialog. However, this
622 doesn't work for other tests (e.g. ident tests) so we have explicit '<' and
623 '>' flags for input and output as well as the defaults. */
625 for (; s; s = s->next)
629 /* Output lines either start with '>' or a digit. In the '>' case we can
630 fudge the sending of \r\n as required. Default is \r\n, ">>" send nothing,
631 ">CR>" sends \r only, and ">LF>" sends \n only. We can also force a
632 connection closedown by ">*eof". */
637 unsigned len = s->len;
638 printit(ss++, len--);
640 if (strncmp(ss, "*eof", 4) == 0)
647 { end = ""; ss++; len--; }
648 else if (strncmp(ss, "CR>", 3) == 0)
649 { end = "\r"; ss += 3; len -= 3; }
650 else if (strncmp(ss, "LF>", 3) == 0)
651 { end = "\n"; ss += 3; len -= 3; }
653 fwrite(ss, 1, len, out);
654 if (*end) fprintf(out, end);
657 else if (isdigit((unsigned char)ss[0]))
660 fprintf(out, "%s\r\n", ss);
663 /* If the script line starts with "*sleep" we just sleep for a while
664 before continuing. */
666 else if (strncmp(ss, "*sleep ", 7) == 0)
668 int sleepfor = atoi(ss+7);
674 /* If the script line starts with "*data " we expect a numeric argument,
675 and we expect to read (and discard) that many data bytes from the input. */
677 else if (strncmp(ss, "*data ", 6) == 0)
679 int dlen = atoi(ss+6);
687 n = dlen < sizeof(buffer) ? dlen : sizeof(buffer);
688 if ((n = read(dup_accept_socket, CS buffer, n)) == 0)
690 printf("Unexpected EOF read from client\n");
698 if (fgetc(in) == EOF)
700 printf("Unexpected EOF read from client\n");
706 /* Otherwise the script line is the start of an input line we are expecting
707 from the client, or "*eof" indicating we expect the client to close the
708 connection. Read command line or data lines; the latter are indicated
709 by the expected line being just ".". If the line starts with '<', that
710 doesn't form part of the expected input. (This allows for incoming data
711 starting with a digit.) If the line starts with '<<' we operate in
712 unbuffered rather than line mode and assume that a single read gets the
718 int data = strcmp(ss, ".") == 0;
742 n = read(dup_accept_socket, CS buffer+offset, s->len - offset);
743 if (content_length.in_use) content_length.left -= n;
746 printf("%sxpected EOF read from client\n",
747 (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
752 while (read(dup_accept_socket, &c, 1) == 1 && c != '\n') ;
756 printit(CS buffer, n);
760 n = (read(dup_accept_socket, &c, 1) == 1 && c == '.');
761 if (content_length.in_use) content_length.left--;
762 while (c != '\n' && read(dup_accept_socket, &c, 1) == 1)
763 if (content_length.in_use) content_length.left--;
765 else if (memcmp(ss, buffer, n) != 0)
767 printf("Comparison failed - bailing out\nExpected: ");
778 if (fgets(CS buffer+offset, sizeof(buffer)-offset, in) == NULL)
780 printf("%sxpected EOF read from client\n",
781 (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
786 n = strlen(CS buffer);
787 if (content_length.in_use) content_length.left -= (n - offset);
788 while (n > 0 && isspace(buffer[n-1])) n--;
790 printf("%s\n", buffer);
791 if (!data || strcmp(CS buffer, ".") == 0) break;
794 if (strncmp(ss, CS buffer, (int)strlen(ss)) != 0)
796 printf("Comparison failed - bailing out\n");
797 printf("Expected: %s\n", ss);
802 if (sscanf(CCS buffer, "<Content-length: %d", &content_length.left))
803 content_length.in_use = TRUE;
804 if (content_length.in_use && content_length.left <= 0)
805 shutdown(dup_accept_socket, SHUT_RD);
814 if (s == NULL) printf("End of script\n");
816 if (sockname) unlink(sockname);
820 /* End of server.c */