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>
30 #ifdef HAVE_NETINET_IP_VAR_H
31 # include <netinet/ip_var.h>
35 #include <arpa/inet.h>
37 #include <sys/resource.h>
38 #include <sys/socket.h>
50 # define S_ADDR_TYPE u_long
55 # define CCS (const char *)
65 typedef unsigned BOOL;
70 /*************************************************
71 * SIGALRM handler - crash out *
72 *************************************************/
76 sigalrm_handler(int sig)
78 sig = sig; /* Keep picky compilers happy */
79 printf("\nServer timed out\n");
80 exit(tmo_noerror ? 0 : 99);
84 /*************************************************
85 * Get textual IP address *
86 *************************************************/
88 /* This function is copied from Exim */
91 host_ntoa(const void *arg, char *buffer)
95 /* The new world. It is annoying that we have to fish out the address from
96 different places in the block, depending on what kind of address it is. It
97 is also a pain that inet_ntop() returns a const char *, whereas the IPv4
98 function inet_ntoa() returns just char *, and some picky compilers insist
99 on warning if one assigns a const char * to a char *. Hence the casts. */
102 char addr_buffer[46];
103 int family = ((struct sockaddr *)arg)->sa_family;
104 if (family == AF_INET6)
106 struct sockaddr_in6 *sk = (struct sockaddr_in6 *)arg;
107 yield = (char *)inet_ntop(family, &(sk->sin6_addr), addr_buffer,
108 sizeof(addr_buffer));
112 struct sockaddr_in *sk = (struct sockaddr_in *)arg;
113 yield = (char *)inet_ntop(family, &(sk->sin_addr), addr_buffer,
114 sizeof(addr_buffer));
117 /* If the result is a mapped IPv4 address, show it in V4 format. */
119 if (strncmp(yield, "::ffff:", 7) == 0) yield += 7;
121 #else /* HAVE_IPV6 */
125 yield = inet_ntoa(((struct sockaddr_in *)arg)->sin_addr);
128 strcpy(buffer, yield);
135 printit(char * s, int n)
139 unsigned char c = *s++;
142 else if (c >= ' ' && c <= '~') /* assumes ascii */
145 printf("\\x%02x", c);
152 /*************************************************
154 *************************************************/
156 #define v6n 0 /* IPv6 socket number */
157 #define v4n 1 /* IPv4 socket number */
158 #define udn 2 /* Unix domain socket number */
159 #define skn 2 /* Potential number of sockets */
161 int main(int argc, char **argv)
165 int listen_socket[3] = { -1, -1, -1 };
167 int dup_accept_socket;
168 int connection_count = 1;
172 int initial_pause = 0;
182 char *pidfile = NULL;
184 char *sockname = NULL;
185 unsigned char buffer[10240];
187 struct sockaddr_un sockun; /* don't use "sun" */
188 struct sockaddr_un sockun_accepted;
189 int sockun_len = sizeof(sockun_accepted);
192 struct sockaddr_in6 sin6;
193 struct sockaddr_in6 accepted;
194 struct in6_addr anyaddr6 = IN6ADDR_ANY_INIT ;
196 struct sockaddr_in accepted;
199 /* Always need an IPv4 structure */
201 struct sockaddr_in sin4;
203 int len = sizeof(accepted);
206 /* Sort out the arguments */
207 if (argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")))
209 printf("Usage: %s [options] port|socket [connection count]\n", argv[0]);
212 "\n\t-i n n seconds initial delay"
213 "\n\t-noipv4 disable ipv4"
214 "\n\t-noipv6 disable ipv6"
215 "\n\t-oP file write PID to file"
216 "\n\t-t n n seconds timeout"
221 while (na < argc && argv[na][0] == '-')
223 if (strcmp(argv[na], "-d") == 0) debug = 1;
224 else if (strcmp(argv[na], "-t") == 0)
226 if (tmo_noerror = ((timeout = atoi(argv[++na])) < 0)) timeout = -timeout;
228 else if (strcmp(argv[na], "-i") == 0) initial_pause = atoi(argv[++na]);
229 else if (strcmp(argv[na], "-noipv4") == 0) use_ipv4 = 0;
230 else if (strcmp(argv[na], "-noipv6") == 0) use_ipv6 = 0;
231 else if (strcmp(argv[na], "-oP") == 0) pidfile = argv[++na];
234 printf("server: unknown option %s, try -h or --help\n", argv[na]);
240 if (!use_ipv4 && !use_ipv6)
242 printf("server: -noipv4 and -noipv6 cannot both be given\n");
248 printf("server: no port number or socket name given\n");
252 if (argv[na][0] == '/')
255 unlink(sockname); /* in case left lying around */
257 else port = atoi(argv[na]);
260 if (na < argc) connection_count = atoi(argv[na]);
263 /* Initial pause (before creating listen sockets */
264 if (initial_pause > 0)
267 printf("%d: Inital pause of %d seconds\n", time(NULL), initial_pause);
269 printf("Inital pause of %d seconds\n", initial_pause);
270 while (initial_pause > 0)
271 initial_pause = sleep(initial_pause);
276 if (port == 0) /* Unix domain */
278 if (debug) printf("%d: Creating Unix domain socket\n", time(NULL));
279 listen_socket[udn] = socket(PF_UNIX, SOCK_STREAM, 0);
280 if (listen_socket[udn] < 0)
282 printf("Unix domain socket creation failed: %s\n", strerror(errno));
291 if (debug) printf("Creating IPv6 socket\n");
292 listen_socket[v6n] = socket(AF_INET6, SOCK_STREAM, 0);
293 if (listen_socket[v6n] < 0)
295 printf("IPv6 socket creation failed: %s\n", strerror(errno));
299 /* If this is an IPv6 wildcard socket, set IPV6_V6ONLY if that option is
303 if (setsockopt(listen_socket[v6n], IPPROTO_IPV6, IPV6_V6ONLY, (char *)(&on),
305 printf("Setting IPV6_V6ONLY on IPv6 wildcard "
306 "socket failed (%s): carrying on without it\n", strerror(errno));
307 #endif /* IPV6_V6ONLY */
309 #endif /* HAVE_IPV6 */
311 /* Create an IPv4 socket if required */
315 if (debug) printf("Creating IPv4 socket\n");
316 listen_socket[v4n] = socket(AF_INET, SOCK_STREAM, 0);
317 if (listen_socket[v4n] < 0)
319 printf("IPv4 socket creation failed: %s\n", strerror(errno));
326 /* Set SO_REUSEADDR on the IP sockets so that the program can be restarted
327 while a connection is being handled - this can happen as old connections lie
328 around for a bit while crashed processes are tidied away. Without this, a
329 connection will prevent reuse of the smtp port for listening. */
331 for (i = v6n; i <= v4n; i++)
333 if (listen_socket[i] >= 0 &&
334 setsockopt(listen_socket[i], SOL_SOCKET, SO_REUSEADDR, (char *)(&on),
337 printf("setting SO_REUSEADDR on socket failed: %s\n", strerror(errno));
343 /* Now bind the sockets to the required port or path. If a path, ensure
344 anyone can write to it. */
349 sockun.sun_family = AF_UNIX;
350 if (debug) printf("Binding Unix domain socket\n");
351 sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1), sockname);
352 if (bind(listen_socket[udn], (struct sockaddr *)&sockun, sizeof(sockun)) < 0)
354 printf("Unix domain socket bind() failed: %s\n", strerror(errno));
357 (void)stat(sockname, &statbuf);
358 if (debug) printf("Setting Unix domain socket mode: %0x\n",
359 statbuf.st_mode | 0777);
360 if (chmod(sockname, statbuf.st_mode | 0777) < 0)
362 printf("Unix domain socket chmod() failed: %s\n", strerror(errno));
369 for (i = 0; i < skn; i++)
371 if (listen_socket[i] < 0) continue;
373 /* For an IPv6 listen, use an IPv6 socket */
378 memset(&sin6, 0, sizeof(sin6));
379 sin6.sin6_family = AF_INET6;
380 sin6.sin6_port = htons(port);
381 sin6.sin6_addr = anyaddr6;
382 if (bind(listen_socket[i], (struct sockaddr *)&sin6, sizeof(sin6)) < 0)
384 printf("IPv6 socket bind() failed: %s\n", strerror(errno));
391 /* For an IPv4 bind, use an IPv4 socket, even in an IPv6 world. If an IPv4
392 bind fails EADDRINUSE after IPv6 success, carry on, because it means the
393 IPv6 socket will handle IPv4 connections. */
396 memset(&sin4, 0, sizeof(sin4));
397 sin4.sin_family = AF_INET;
398 sin4.sin_addr.s_addr = (S_ADDR_TYPE)INADDR_ANY;
399 sin4.sin_port = htons(port);
400 if (bind(listen_socket[i], (struct sockaddr *)&sin4, sizeof(sin4)) < 0)
401 if (listen_socket[v6n] < 0 || errno != EADDRINUSE)
403 printf("IPv4 socket bind() failed: %s\n", strerror(errno));
408 close(listen_socket[i]);
409 listen_socket[i] = -1;
416 /* Start listening. If IPv4 fails EADDRINUSE after IPv6 succeeds, ignore the
417 error because it means that the IPv6 socket will handle IPv4 connections. Don't
418 output anything, because it will mess up the test output, which will be
419 different for systems that do this and those that don't. */
421 for (i = 0; i <= skn; i++)
423 if (listen_socket[i] >= 0 && listen(listen_socket[i], 5) < 0)
425 if (i != v4n || listen_socket[v6n] < 0 || errno != EADDRINUSE)
427 printf("listen() failed: %s\n", strerror(errno));
437 if (!(p = fopen(pidfile, "w")))
439 fprintf(stderr, "pidfile create failed: %s\n", strerror(errno));
442 fprintf(p, "%ld\n", (long)getpid());
446 /* This program handles only a fixed number of connections, in sequence. Before
447 waiting for the first connection, read the standard input, which contains the
448 script of things to do. A line containing "++++" is treated as end of file.
449 This is so that the Perl driving script doesn't have to close the pipe -
450 because that would cause it to wait for this process, which it doesn't yet want
451 to do. The driving script adds the "++++" automatically - it doesn't actually
452 appear in the test script. Within lines we interpret \xNN and \\ groups */
454 while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
458 int n = (int)strlen(CS buffer);
460 if (n > 1 && buffer[0] == '>' && buffer[1] == '>')
462 while (n > 0 && isspace(buffer[n-1])) n--;
464 if (strcmp(CS buffer, "++++") == 0) break;
465 next = malloc(sizeof(line) + n);
469 char * s = CS buffer;
474 if (cl == '\\' && (cl = *++s) == 'x')
476 if ((ch = *++s - '0') > 9 && (ch -= 'A'-'9'-1) > 15) ch -= 'a'-'A';
477 if ((cl = *++s - '0') > 9 && (cl -= 'A'-'9'-1) > 15) cl -= 'a'-'A';
484 next->len = d - next->line - 1;
485 if (last == NULL) script = last = next;
486 else last->next = next;
492 /* SIGALRM handler crashes out */
494 signal(SIGALRM, sigalrm_handler);
496 /* s points to the current place in the script */
500 for (count = 0; count < connection_count; count++)
505 } content_length = { 0, FALSE };
510 printf("Listening on %s ... ", sockname);
512 accept_socket = accept(listen_socket[udn],
513 (struct sockaddr *)&sockun_accepted, &sockun_len);
520 fd_set select_listen;
522 printf("Listening on port %d ... ", port);
525 FD_ZERO(&select_listen);
526 for (i = 0; i < skn; i++)
528 if (listen_socket[i] >= 0) FD_SET(listen_socket[i], &select_listen);
529 if (listen_socket[i] > max_socket) max_socket = listen_socket[i];
532 if ((lcount = select(max_socket + 1, &select_listen, NULL, NULL, NULL)) < 0)
534 printf("Select failed\n");
540 for (i = 0; i < skn; i++)
541 if (listen_socket[i] > 0 && FD_ISSET(listen_socket[i], &select_listen))
543 accept_socket = accept(listen_socket[i],
544 (struct sockaddr *)&accepted, &len);
545 FD_CLR(listen_socket[i], &select_listen);
551 if (accept_socket < 0)
553 printf("accept() failed: %s\n", strerror(errno));
557 out = fdopen(accept_socket, "w");
559 dup_accept_socket = dup(accept_socket);
562 printf("\nConnection request from [%s]\n", host_ntoa(&accepted, CS buffer));
565 printf("\nConnection request\n");
567 /* Linux supports a feature for acquiring the peer's credentials, but it
568 appears to be Linux-specific. This code is untested and unused, just
569 saved here for reference. */
571 /**********--------------------
575 if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl)==0) {
576 printf("Peer's pid=%d, uid=%d, gid=%d\n",
577 cr.pid, cr.uid, cr.gid);
578 --------------*****************/
582 if (dup_accept_socket < 0)
584 printf("Couldn't dup socket descriptor\n");
585 printf("421 Connection refused: %s\n", strerror(errno));
586 fprintf(out, "421 Connection refused: %s\r\n", strerror(errno));
591 in = fdopen(dup_accept_socket, "r");
593 /* Loop for handling the conversation(s). For use in SMTP sessions, there are
594 default rules for determining input and output lines: the latter start with
595 digits. This means that the input looks like SMTP dialog. However, this
596 doesn't work for other tests (e.g. ident tests) so we have explicit '<' and
597 '>' flags for input and output as well as the defaults. */
599 for (; s; s = s->next)
603 /* Output lines either start with '>' or a digit. In the '>' case we can
604 fudge the sending of \r\n as required. Default is \r\n, ">>" send nothing,
605 ">CR>" sends \r only, and ">LF>" sends \n only. We can also force a
606 connection closedown by ">*eof". */
611 unsigned len = s->len;
612 printit(ss++, len--);
614 if (strncmp(ss, "*eof", 4) == 0)
621 { end = ""; ss++; len--; }
622 else if (strncmp(ss, "CR>", 3) == 0)
623 { end = "\r"; ss += 3; len -= 3; }
624 else if (strncmp(ss, "LF>", 3) == 0)
625 { end = "\n"; ss += 3; len -= 3; }
627 fwrite(ss, 1, len, out);
628 if (*end) fprintf(out, end);
631 else if (isdigit((unsigned char)ss[0]))
634 fprintf(out, "%s\r\n", ss);
637 /* If the script line starts with "*sleep" we just sleep for a while
638 before continuing. */
640 else if (strncmp(ss, "*sleep ", 7) == 0)
642 int sleepfor = atoi(ss+7);
648 /* If the script line starts with "*data " we expect a numeric argument,
649 and we expect to read (and discard) that many data bytes from the input. */
651 else if (strncmp(ss, "*data ", 6) == 0)
653 int dlen = atoi(ss+6);
661 n = dlen < sizeof(buffer) ? dlen : sizeof(buffer);
662 if ((n = read(dup_accept_socket, CS buffer, n)) == 0)
664 printf("Unexpected EOF read from client\n");
672 if (fgetc(in) == EOF)
674 printf("Unexpected EOF read from client\n");
680 /* Otherwise the script line is the start of an input line we are expecting
681 from the client, or "*eof" indicating we expect the client to close the
682 connection. Read command line or data lines; the latter are indicated
683 by the expected line being just ".". If the line starts with '<', that
684 doesn't form part of the expected input. (This allows for incoming data
685 starting with a digit.) If the line starts with '<<' we operate in
686 unbuffered rather than line mode and assume that a single read gets the
692 int data = strcmp(ss, ".") == 0;
716 n = read(dup_accept_socket, CS buffer+offset, s->len - offset);
717 if (content_length.in_use) content_length.left -= n;
720 printf("%sxpected EOF read from client\n",
721 (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
726 while (read(dup_accept_socket, &c, 1) == 1 && c != '\n') ;
730 printit(CS buffer, n);
734 n = (read(dup_accept_socket, &c, 1) == 1 && c == '.');
735 if (content_length.in_use) content_length.left--;
736 while (c != '\n' && read(dup_accept_socket, &c, 1) == 1)
737 if (content_length.in_use) content_length.left--;
739 else if (memcmp(ss, buffer, n) != 0)
741 printf("Comparison failed - bailing out\nExpected: ");
752 if (fgets(CS buffer+offset, sizeof(buffer)-offset, in) == NULL)
754 printf("%sxpected EOF read from client\n",
755 (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
760 n = strlen(CS buffer);
761 if (content_length.in_use) content_length.left -= (n - offset);
762 while (n > 0 && isspace(buffer[n-1])) n--;
764 printf("%s\n", buffer);
765 if (!data || strcmp(CS buffer, ".") == 0) break;
768 if (strncmp(ss, CS buffer, (int)strlen(ss)) != 0)
770 printf("Comparison failed - bailing out\n");
771 printf("Expected: %s\n", ss);
776 if (sscanf(CCS buffer, "<Content-length: %d", &content_length.left))
777 content_length.in_use = TRUE;
778 if (content_length.in_use && content_length.left <= 0)
779 shutdown(dup_accept_socket, SHUT_RD);
788 if (s == NULL) printf("End of script\n");
790 if (sockname) unlink(sockname);
794 /* End of server.c */