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 */
162 int main(int argc, char **argv)
166 int listen_socket[3] = { -1, -1, -1 };
168 int dup_accept_socket;
169 int connection_count = 1;
173 int initial_pause = 0, tfo = 0;
183 char *pidfile = NULL;
185 char *sockname = NULL;
186 unsigned char buffer[10240];
188 struct sockaddr_un sockun; /* don't use "sun" */
189 struct sockaddr_un sockun_accepted;
190 int sockun_len = sizeof(sockun_accepted);
193 struct sockaddr_in6 sin6;
194 struct sockaddr_in6 accepted;
195 struct in6_addr anyaddr6 = IN6ADDR_ANY_INIT ;
197 struct sockaddr_in accepted;
200 /* Always need an IPv4 structure */
202 struct sockaddr_in sin4;
204 int len = sizeof(accepted);
207 /* Sort out the arguments */
208 if (argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")))
210 printf("Usage: %s [options] port|socket [connection count]\n", argv[0]);
213 "\n\t-i n n seconds initial delay"
214 "\n\t-noipv4 disable ipv4"
215 "\n\t-noipv6 disable ipv6"
216 "\n\t-oP file write PID to file"
217 "\n\t-t n n seconds timeout"
218 "\n\t-tfo enable TCP Fast Open"
223 while (na < argc && argv[na][0] == '-')
225 if (strcmp(argv[na], "-d") == 0) debug = 1;
226 else if (strcmp(argv[na], "-tfo") == 0) tfo = 1;
227 else if (strcmp(argv[na], "-t") == 0)
229 if (tmo_noerror = ((timeout = atoi(argv[++na])) < 0)) timeout = -timeout;
231 else if (strcmp(argv[na], "-i") == 0) initial_pause = atoi(argv[++na]);
232 else if (strcmp(argv[na], "-noipv4") == 0) use_ipv4 = 0;
233 else if (strcmp(argv[na], "-noipv6") == 0) use_ipv6 = 0;
234 else if (strcmp(argv[na], "-oP") == 0) pidfile = argv[++na];
237 printf("server: unknown option %s, try -h or --help\n", argv[na]);
243 if (!use_ipv4 && !use_ipv6)
245 printf("server: -noipv4 and -noipv6 cannot both be given\n");
251 printf("server: no port number or socket name given\n");
255 if (argv[na][0] == '/')
258 unlink(sockname); /* in case left lying around */
260 else port = atoi(argv[na]);
263 if (na < argc) connection_count = atoi(argv[na]);
266 /* Initial pause (before creating listen sockets */
267 if (initial_pause > 0)
270 printf("%d: Inital pause of %d seconds\n", time(NULL), initial_pause);
272 printf("Inital pause of %d seconds\n", initial_pause);
273 while (initial_pause > 0)
274 initial_pause = sleep(initial_pause);
279 if (port == 0) /* Unix domain */
281 if (debug) printf("%d: Creating Unix domain socket\n", time(NULL));
282 listen_socket[udn] = socket(PF_UNIX, SOCK_STREAM, 0);
283 if (listen_socket[udn] < 0)
285 printf("Unix domain socket creation failed: %s\n", strerror(errno));
294 if (debug) printf("Creating IPv6 socket\n");
295 listen_socket[v6n] = socket(AF_INET6, SOCK_STREAM, 0);
296 if (listen_socket[v6n] < 0)
298 printf("IPv6 socket creation failed: %s\n", strerror(errno));
305 if (setsockopt(listen_socket[v6n], IPPROTO_TCP, TCP_FASTOPEN,
306 &backlog, sizeof(backlog)))
307 if (debug) printf("setsockopt TCP_FASTOPEN: %s\n", strerror(errno));
310 /* If this is an IPv6 wildcard socket, set IPV6_V6ONLY if that option is
314 if (setsockopt(listen_socket[v6n], IPPROTO_IPV6, IPV6_V6ONLY, (char *)(&on),
316 printf("Setting IPV6_V6ONLY on IPv6 wildcard "
317 "socket failed (%s): carrying on without it\n", strerror(errno));
318 #endif /* IPV6_V6ONLY */
320 #endif /* HAVE_IPV6 */
322 /* Create an IPv4 socket if required */
326 if (debug) printf("Creating IPv4 socket\n");
327 listen_socket[v4n] = socket(AF_INET, SOCK_STREAM, 0);
328 if (listen_socket[v4n] < 0)
330 printf("IPv4 socket creation failed: %s\n", strerror(errno));
337 if (setsockopt(listen_socket[v4n], IPPROTO_TCP, TCP_FASTOPEN,
338 &backlog, sizeof(backlog)))
339 if (debug) printf("setsockopt TCP_FASTOPEN: %s\n", strerror(errno));
346 /* Set SO_REUSEADDR on the IP sockets so that the program can be restarted
347 while a connection is being handled - this can happen as old connections lie
348 around for a bit while crashed processes are tidied away. Without this, a
349 connection will prevent reuse of the smtp port for listening. */
351 for (i = v6n; i <= v4n; i++)
353 if (listen_socket[i] >= 0 &&
354 setsockopt(listen_socket[i], SOL_SOCKET, SO_REUSEADDR, (char *)(&on),
357 printf("setting SO_REUSEADDR on socket failed: %s\n", strerror(errno));
363 /* Now bind the sockets to the required port or path. If a path, ensure
364 anyone can write to it. */
369 sockun.sun_family = AF_UNIX;
370 if (debug) printf("Binding Unix domain socket\n");
371 sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1), sockname);
372 if (bind(listen_socket[udn], (struct sockaddr *)&sockun, sizeof(sockun)) < 0)
374 printf("Unix domain socket bind() failed: %s\n", strerror(errno));
377 (void)stat(sockname, &statbuf);
378 if (debug) printf("Setting Unix domain socket mode: %0x\n",
379 statbuf.st_mode | 0777);
380 if (chmod(sockname, statbuf.st_mode | 0777) < 0)
382 printf("Unix domain socket chmod() failed: %s\n", strerror(errno));
389 for (i = 0; i < skn; i++)
391 if (listen_socket[i] < 0) continue;
393 /* For an IPv6 listen, use an IPv6 socket */
398 memset(&sin6, 0, sizeof(sin6));
399 sin6.sin6_family = AF_INET6;
400 sin6.sin6_port = htons(port);
401 sin6.sin6_addr = anyaddr6;
402 if (bind(listen_socket[i], (struct sockaddr *)&sin6, sizeof(sin6)) < 0)
404 printf("IPv6 socket bind(port %d) failed: %s\n", port, strerror(errno));
411 /* For an IPv4 bind, use an IPv4 socket, even in an IPv6 world. If an IPv4
412 bind fails EADDRINUSE after IPv6 success, carry on, because it means the
413 IPv6 socket will handle IPv4 connections. */
416 memset(&sin4, 0, sizeof(sin4));
417 sin4.sin_family = AF_INET;
418 sin4.sin_addr.s_addr = (S_ADDR_TYPE)INADDR_ANY;
419 sin4.sin_port = htons(port);
420 if (bind(listen_socket[i], (struct sockaddr *)&sin4, sizeof(sin4)) < 0)
421 if (listen_socket[v6n] < 0 || errno != EADDRINUSE)
423 printf("IPv4 socket bind(port %d) failed: %s\n", port, strerror(errno));
428 close(listen_socket[i]);
429 listen_socket[i] = -1;
436 /* Start listening. If IPv4 fails EADDRINUSE after IPv6 succeeds, ignore the
437 error because it means that the IPv6 socket will handle IPv4 connections. Don't
438 output anything, because it will mess up the test output, which will be
439 different for systems that do this and those that don't. */
441 for (i = 0; i <= skn; i++)
443 if (listen_socket[i] >= 0 && listen(listen_socket[i], 5) < 0)
445 if (i != v4n || listen_socket[v6n] < 0 || errno != EADDRINUSE)
447 printf("listen() failed: %s\n", strerror(errno));
457 if (!(p = fopen(pidfile, "w")))
459 fprintf(stderr, "pidfile create failed: %s\n", strerror(errno));
462 fprintf(p, "%ld\n", (long)getpid());
466 /* This program handles only a fixed number of connections, in sequence. Before
467 waiting for the first connection, read the standard input, which contains the
468 script of things to do. A line containing "++++" is treated as end of file.
469 This is so that the Perl driving script doesn't have to close the pipe -
470 because that would cause it to wait for this process, which it doesn't yet want
471 to do. The driving script adds the "++++" automatically - it doesn't actually
472 appear in the test script. Within lines we interpret \xNN and \\ groups */
474 while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
478 int n = (int)strlen(CS buffer);
480 if (n > 1 && buffer[0] == '>' && buffer[1] == '>')
482 while (n > 0 && isspace(buffer[n-1])) n--;
484 if (strcmp(CS buffer, "++++") == 0) break;
485 next = malloc(sizeof(line) + n);
489 char * s = CS buffer;
494 if (cl == '\\' && (cl = *++s) == 'x')
496 if ((ch = *++s - '0') > 9 && (ch -= 'A'-'9'-1) > 15) ch -= 'a'-'A';
497 if ((cl = *++s - '0') > 9 && (cl -= 'A'-'9'-1) > 15) cl -= 'a'-'A';
504 next->len = d - next->line - 1;
505 if (last == NULL) script = last = next;
506 else last->next = next;
512 /* SIGALRM handler crashes out */
514 signal(SIGALRM, sigalrm_handler);
516 /* s points to the current place in the script */
520 for (count = 0; count < connection_count; count++)
525 printf("Listening on %s ... ", sockname);
527 accept_socket = accept(listen_socket[udn],
528 (struct sockaddr *)&sockun_accepted, &sockun_len);
535 fd_set select_listen;
537 printf("Listening on port %d ... ", port);
540 FD_ZERO(&select_listen);
541 for (i = 0; i < skn; i++)
543 if (listen_socket[i] >= 0) FD_SET(listen_socket[i], &select_listen);
544 if (listen_socket[i] > max_socket) max_socket = listen_socket[i];
547 if ((lcount = select(max_socket + 1, &select_listen, NULL, NULL, NULL)) < 0)
549 printf("Select failed\n");
555 for (i = 0; i < skn; i++)
556 if (listen_socket[i] > 0 && FD_ISSET(listen_socket[i], &select_listen))
558 accept_socket = accept(listen_socket[i],
559 (struct sockaddr *)&accepted, &len);
560 FD_CLR(listen_socket[i], &select_listen);
566 if (accept_socket < 0)
568 printf("accept() failed: %s\n", strerror(errno));
572 out = fdopen(accept_socket, "w");
574 dup_accept_socket = dup(accept_socket);
577 printf("\nConnection request from [%s]\n", host_ntoa(&accepted, CS buffer));
580 printf("\nConnection request\n");
582 /* Linux supports a feature for acquiring the peer's credentials, but it
583 appears to be Linux-specific. This code is untested and unused, just
584 saved here for reference. */
586 /**********--------------------
590 if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl)==0) {
591 printf("Peer's pid=%d, uid=%d, gid=%d\n",
592 cr.pid, cr.uid, cr.gid);
593 --------------*****************/
597 if (dup_accept_socket < 0)
599 printf("Couldn't dup socket descriptor\n");
600 printf("421 Connection refused: %s\n", strerror(errno));
601 fprintf(out, "421 Connection refused: %s\r\n", strerror(errno));
606 in = fdopen(dup_accept_socket, "r");
608 /* Loop for handling the conversation(s). For use in SMTP sessions, there are
609 default rules for determining input and output lines: the latter start with
610 digits. This means that the input looks like SMTP dialog. However, this
611 doesn't work for other tests (e.g. ident tests) so we have explicit '<' and
612 '>' flags for input and output as well as the defaults. */
614 for (; s; s = s->next)
618 /* Output lines either start with '>' or a digit. In the '>' case we can
619 fudge the sending of \r\n as required. Default is \r\n, ">>" send nothing,
620 ">CR>" sends \r only, and ">LF>" sends \n only. We can also force a
621 connection closedown by ">*eof". */
626 unsigned len = s->len;
627 printit(ss++, len--);
629 if (strncmp(ss, "*eof", 4) == 0)
636 { end = ""; ss++; len--; }
637 else if (strncmp(ss, "CR>", 3) == 0)
638 { end = "\r"; ss += 3; len -= 3; }
639 else if (strncmp(ss, "LF>", 3) == 0)
640 { end = "\n"; ss += 3; len -= 3; }
642 fwrite(ss, 1, len, out);
643 if (*end) fprintf(out, end);
646 else if (isdigit((unsigned char)ss[0]))
649 fprintf(out, "%s\r\n", ss);
652 /* If the script line starts with "*sleep" we just sleep for a while
653 before continuing. */
655 else if (strncmp(ss, "*sleep ", 7) == 0)
657 int sleepfor = atoi(ss+7);
663 /* If the script line starts with "*data " we expect a numeric argument,
664 and we expect to read (and discard) that many data bytes from the input. */
666 else if (strncmp(ss, "*data ", 6) == 0)
668 int dlen = atoi(ss+6);
676 n = dlen < sizeof(buffer) ? dlen : sizeof(buffer);
677 if ((n = read(dup_accept_socket, CS buffer, n)) == 0)
679 printf("Unexpected EOF read from client\n");
687 if (fgetc(in) == EOF)
689 printf("Unexpected EOF read from client\n");
695 /* Otherwise the script line is the start of an input line we are expecting
696 from the client, or "*eof" indicating we expect the client to close the
697 connection. Read command line or data lines; the latter are indicated
698 by the expected line being just ".". If the line starts with '<', that
699 doesn't form part of the expected input. (This allows for incoming data
700 starting with a digit.) If the line starts with '<<' we operate in
701 unbuffered rather than line mode and assume that a single read gets the
707 int data = strcmp(ss, ".") == 0;
731 n = read(dup_accept_socket, CS buffer+offset, s->len - offset);
734 printf("%sxpected EOF read from client\n",
735 (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
740 while (read(dup_accept_socket, &c, 1) == 1 && c != '\n') ;
744 printit(CS buffer, n);
748 n = (read(dup_accept_socket, &c, 1) == 1 && c == '.');
749 while (c != '\n' && read(dup_accept_socket, &c, 1) == 1)
752 else if (memcmp(ss, buffer, n) != 0)
754 printf("Comparison failed - bailing out\nExpected: ");
765 if (fgets(CS buffer+offset, sizeof(buffer)-offset, in) == NULL)
767 printf("%sxpected EOF read from client\n",
768 (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
773 n = (int)strlen(CS buffer);
774 while (n > 0 && isspace(buffer[n-1])) n--;
776 printf("%s\n", buffer);
777 if (!data || strcmp(CS buffer, ".") == 0) break;
780 if (strncmp(ss, CS buffer, (int)strlen(ss)) != 0)
782 printf("Comparison failed - bailing out\n");
783 printf("Expected: %s\n", ss);
795 if (s == NULL) printf("End of script\n");
797 if (sockname) unlink(sockname);
801 /* End of server.c */