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
65 /*************************************************
66 * SIGALRM handler - crash out *
67 *************************************************/
70 sigalrm_handler(int sig)
72 sig = sig; /* Keep picky compilers happy */
73 printf("\nServer timed out\n");
78 /*************************************************
79 * Get textual IP address *
80 *************************************************/
82 /* This function is copied from Exim */
85 host_ntoa(const void *arg, char *buffer)
89 /* The new world. It is annoying that we have to fish out the address from
90 different places in the block, depending on what kind of address it is. It
91 is also a pain that inet_ntop() returns a const char *, whereas the IPv4
92 function inet_ntoa() returns just char *, and some picky compilers insist
93 on warning if one assigns a const char * to a char *. Hence the casts. */
97 int family = ((struct sockaddr *)arg)->sa_family;
98 if (family == AF_INET6)
100 struct sockaddr_in6 *sk = (struct sockaddr_in6 *)arg;
101 yield = (char *)inet_ntop(family, &(sk->sin6_addr), addr_buffer,
102 sizeof(addr_buffer));
106 struct sockaddr_in *sk = (struct sockaddr_in *)arg;
107 yield = (char *)inet_ntop(family, &(sk->sin_addr), addr_buffer,
108 sizeof(addr_buffer));
111 /* If the result is a mapped IPv4 address, show it in V4 format. */
113 if (strncmp(yield, "::ffff:", 7) == 0) yield += 7;
115 #else /* HAVE_IPV6 */
119 yield = inet_ntoa(((struct sockaddr_in *)arg)->sin_addr);
122 strcpy(buffer, yield);
129 printit(char * s, int n)
133 unsigned char c = *s++;
136 else if (c >= ' ' && c <= '~') /* assumes ascii */
139 printf("\\x%02x", c);
146 /*************************************************
148 *************************************************/
150 #define v6n 0 /* IPv6 socket number */
151 #define v4n 1 /* IPv4 socket number */
152 #define udn 2 /* Unix domain socket number */
153 #define skn 2 /* Potential number of sockets */
155 int main(int argc, char **argv)
159 int listen_socket[3] = { -1, -1, -1 };
161 int dup_accept_socket;
162 int connection_count = 1;
166 int initial_pause = 0;
176 char *pidfile = NULL;
178 char *sockname = NULL;
179 unsigned char buffer[10240];
181 struct sockaddr_un sockun; /* don't use "sun" */
182 struct sockaddr_un sockun_accepted;
183 int sockun_len = sizeof(sockun_accepted);
186 struct sockaddr_in6 sin6;
187 struct sockaddr_in6 accepted;
188 struct in6_addr anyaddr6 = IN6ADDR_ANY_INIT ;
190 struct sockaddr_in accepted;
193 /* Always need an IPv4 structure */
195 struct sockaddr_in sin4;
197 int len = sizeof(accepted);
200 /* Sort out the arguments */
201 if (argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")))
203 printf("Usage: %s [options]\n", argv[0]);
206 "\n\t-i n n seconds initial delay"
207 "\n\t-noipv4 disable ipv4"
208 "\n\t-noipv6 disable ipv6"
209 "\n\t-oP file write PID to file"
210 "\n\t-t n n seconds timeout"
215 while (na < argc && argv[na][0] == '-')
217 if (strcmp(argv[na], "-d") == 0) debug = 1;
218 else if (strcmp(argv[na], "-t") == 0) timeout = atoi(argv[++na]);
219 else if (strcmp(argv[na], "-i") == 0) initial_pause = atoi(argv[++na]);
220 else if (strcmp(argv[na], "-noipv4") == 0) use_ipv4 = 0;
221 else if (strcmp(argv[na], "-noipv6") == 0) use_ipv6 = 0;
222 else if (strcmp(argv[na], "-oP") == 0) pidfile = argv[++na];
225 printf("server: unknown option %s, try -h or --help\n", argv[na]);
231 if (!use_ipv4 && !use_ipv6)
233 printf("server: -noipv4 and -noipv6 cannot both be given\n");
239 printf("server: no port number or socket name given\n");
243 if (argv[na][0] == '/')
246 unlink(sockname); /* in case left lying around */
248 else port = atoi(argv[na]);
251 if (na < argc) connection_count = atoi(argv[na]);
254 /* Initial pause (before creating listen sockets */
255 if (initial_pause > 0)
258 printf("%d: Inital pause of %d seconds\n", time(NULL), initial_pause);
260 printf("Inital pause of %d seconds\n", initial_pause);
261 while (initial_pause > 0)
262 initial_pause = sleep(initial_pause);
267 if (port == 0) /* Unix domain */
269 if (debug) printf("%d: Creating Unix domain socket\n", time(NULL));
270 listen_socket[udn] = socket(PF_UNIX, SOCK_STREAM, 0);
271 if (listen_socket[udn] < 0)
273 printf("Unix domain socket creation failed: %s\n", strerror(errno));
282 if (debug) printf("Creating IPv6 socket\n");
283 listen_socket[v6n] = socket(AF_INET6, SOCK_STREAM, 0);
284 if (listen_socket[v6n] < 0)
286 printf("IPv6 socket creation failed: %s\n", strerror(errno));
290 /* If this is an IPv6 wildcard socket, set IPV6_V6ONLY if that option is
294 if (setsockopt(listen_socket[v6n], IPPROTO_IPV6, IPV6_V6ONLY, (char *)(&on),
296 printf("Setting IPV6_V6ONLY on IPv6 wildcard "
297 "socket failed (%s): carrying on without it\n", strerror(errno));
298 #endif /* IPV6_V6ONLY */
300 #endif /* HAVE_IPV6 */
302 /* Create an IPv4 socket if required */
306 if (debug) printf("Creating IPv4 socket\n");
307 listen_socket[v4n] = socket(AF_INET, SOCK_STREAM, 0);
308 if (listen_socket[v4n] < 0)
310 printf("IPv4 socket creation failed: %s\n", strerror(errno));
317 /* Set SO_REUSEADDR on the IP sockets so that the program can be restarted
318 while a connection is being handled - this can happen as old connections lie
319 around for a bit while crashed processes are tidied away. Without this, a
320 connection will prevent reuse of the smtp port for listening. */
322 for (i = v6n; i <= v4n; i++)
324 if (listen_socket[i] >= 0 &&
325 setsockopt(listen_socket[i], SOL_SOCKET, SO_REUSEADDR, (char *)(&on),
328 printf("setting SO_REUSEADDR on socket failed: %s\n", strerror(errno));
334 /* Now bind the sockets to the required port or path. If a path, ensure
335 anyone can write to it. */
340 sockun.sun_family = AF_UNIX;
341 if (debug) printf("Binding Unix domain socket\n");
342 sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1), sockname);
343 if (bind(listen_socket[udn], (struct sockaddr *)&sockun, sizeof(sockun)) < 0)
345 printf("Unix domain socket bind() failed: %s\n", strerror(errno));
348 (void)stat(sockname, &statbuf);
349 if (debug) printf("Setting Unix domain socket mode: %0x\n",
350 statbuf.st_mode | 0777);
351 if (chmod(sockname, statbuf.st_mode | 0777) < 0)
353 printf("Unix domain socket chmod() failed: %s\n", strerror(errno));
360 for (i = 0; i < skn; i++)
362 if (listen_socket[i] < 0) continue;
364 /* For an IPv6 listen, use an IPv6 socket */
369 memset(&sin6, 0, sizeof(sin6));
370 sin6.sin6_family = AF_INET6;
371 sin6.sin6_port = htons(port);
372 sin6.sin6_addr = anyaddr6;
373 if (bind(listen_socket[i], (struct sockaddr *)&sin6, sizeof(sin6)) < 0)
375 printf("IPv6 socket bind() failed: %s\n", strerror(errno));
382 /* For an IPv4 bind, use an IPv4 socket, even in an IPv6 world. If an IPv4
383 bind fails EADDRINUSE after IPv6 success, carry on, because it means the
384 IPv6 socket will handle IPv4 connections. */
387 memset(&sin4, 0, sizeof(sin4));
388 sin4.sin_family = AF_INET;
389 sin4.sin_addr.s_addr = (S_ADDR_TYPE)INADDR_ANY;
390 sin4.sin_port = htons(port);
391 if (bind(listen_socket[i], (struct sockaddr *)&sin4, sizeof(sin4)) < 0)
393 if (listen_socket[v6n] < 0 || errno != EADDRINUSE)
395 printf("IPv4 socket bind() failed: %s\n", strerror(errno));
400 close(listen_socket[i]);
401 listen_socket[i] = -1;
409 /* Start listening. If IPv4 fails EADDRINUSE after IPv6 succeeds, ignore the
410 error because it means that the IPv6 socket will handle IPv4 connections. Don't
411 output anything, because it will mess up the test output, which will be
412 different for systems that do this and those that don't. */
414 for (i = 0; i <= skn; i++)
416 if (listen_socket[i] >= 0 && listen(listen_socket[i], 5) < 0)
418 if (i != v4n || listen_socket[v6n] < 0 || errno != EADDRINUSE)
420 printf("listen() failed: %s\n", strerror(errno));
430 if (!(p = fopen(pidfile, "w")))
432 fprintf(stderr, "pidfile create failed: %s\n", strerror(errno));
435 fprintf(p, "%ld\n", (long)getpid());
439 /* This program handles only a fixed number of connections, in sequence. Before
440 waiting for the first connection, read the standard input, which contains the
441 script of things to do. A line containing "++++" is treated as end of file.
442 This is so that the Perl driving script doesn't have to close the pipe -
443 because that would cause it to wait for this process, which it doesn't yet want
444 to do. The driving script adds the "++++" automatically - it doesn't actually
445 appear in the test script. Within lines we interpret \xNN and \\ groups */
447 while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
451 int n = (int)strlen(CS buffer);
453 if (n > 1 && buffer[0] == '>' && buffer[1] == '>')
455 while (n > 0 && isspace(buffer[n-1])) n--;
457 if (strcmp(CS buffer, "++++") == 0) break;
458 next = malloc(sizeof(line) + n);
462 char * s = CS buffer;
467 if (cl == '\\' && (cl = *++s) == 'x')
469 if ((ch = *++s - '0') > 9 && (ch -= 'A'-'9'-1) > 15) ch -= 'a'-'A';
470 if ((cl = *++s - '0') > 9 && (cl -= 'A'-'9'-1) > 15) cl -= 'a'-'A';
477 next->len = d - next->line - 1;
478 if (last == NULL) script = last = next;
479 else last->next = next;
485 /* SIGALRM handler crashes out */
487 signal(SIGALRM, sigalrm_handler);
489 /* s points to the current place in the script */
493 for (count = 0; count < connection_count; count++)
498 printf("Listening on %s ... ", sockname);
500 accept_socket = accept(listen_socket[udn],
501 (struct sockaddr *)&sockun_accepted, &sockun_len);
508 fd_set select_listen;
510 printf("Listening on port %d ... ", port);
513 FD_ZERO(&select_listen);
514 for (i = 0; i < skn; i++)
516 if (listen_socket[i] >= 0) FD_SET(listen_socket[i], &select_listen);
517 if (listen_socket[i] > max_socket) max_socket = listen_socket[i];
520 lcount = select(max_socket + 1, &select_listen, NULL, NULL, NULL);
523 printf("Select failed\n");
529 for (i = 0; i < skn; i++)
531 if (listen_socket[i] > 0 && FD_ISSET(listen_socket[i], &select_listen))
533 accept_socket = accept(listen_socket[i],
534 (struct sockaddr *)&accepted, &len);
535 FD_CLR(listen_socket[i], &select_listen);
542 if (accept_socket < 0)
544 printf("accept() failed: %s\n", strerror(errno));
548 out = fdopen(accept_socket, "w");
550 dup_accept_socket = dup(accept_socket);
553 printf("\nConnection request from [%s]\n", host_ntoa(&accepted, CS buffer));
556 printf("\nConnection request\n");
558 /* Linux supports a feature for acquiring the peer's credentials, but it
559 appears to be Linux-specific. This code is untested and unused, just
560 saved here for reference. */
562 /**********--------------------
566 if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl)==0) {
567 printf("Peer's pid=%d, uid=%d, gid=%d\n",
568 cr.pid, cr.uid, cr.gid);
569 --------------*****************/
572 if (dup_accept_socket < 0)
574 printf("Couldn't dup socket descriptor\n");
575 printf("421 Connection refused: %s\n", strerror(errno));
576 fprintf(out, "421 Connection refused: %s\r\n", strerror(errno));
581 in = fdopen(dup_accept_socket, "r");
583 /* Loop for handling the conversation(s). For use in SMTP sessions, there are
584 default rules for determining input and output lines: the latter start with
585 digits. This means that the input looks like SMTP dialog. However, this
586 doesn't work for other tests (e.g. ident tests) so we have explicit '<' and
587 '>' flags for input and output as well as the defaults. */
589 for (; s != NULL; s = s->next)
593 /* Output lines either start with '>' or a digit. In the '>' case we can
594 fudge the sending of \r\n as required. Default is \r\n, ">>" send nothing,
595 ">CR>" sends \r only, and ">LF>" sends \n only. We can also force a
596 connection closedown by ">*eof". */
601 unsigned len = s->len;
602 printit(ss++, len--);
604 if (strncmp(ss, "*eof", 4) == 0)
611 { end = ""; ss++; len--; }
612 else if (strncmp(ss, "CR>", 3) == 0)
613 { end = "\r"; ss += 3; len -= 3; }
614 else if (strncmp(ss, "LF>", 3) == 0)
615 { end = "\n"; ss += 3; len -= 3; }
617 fwrite(ss, 1, len, out);
618 if (*end) fprintf(out, end);
621 else if (isdigit((unsigned char)ss[0]))
624 fprintf(out, "%s\r\n", ss);
627 /* If the script line starts with "*sleep" we just sleep for a while
628 before continuing. */
630 else if (strncmp(ss, "*sleep ", 7) == 0)
632 int sleepfor = atoi(ss+7);
638 /* Otherwise the script line is the start of an input line we are expecting
639 from the client, or "*eof" indicating we expect the client to close the
640 connection. Read command line or data lines; the latter are indicated
641 by the expected line being just ".". If the line starts with '<', that
642 doesn't form part of the expected input. (This allows for incoming data
643 starting with a digit.) If the line starts with '<<' we operate in
644 unbuffered rather than line mode and assume that a single read gets the
650 int data = strcmp(ss, ".") == 0;
674 n = read(dup_accept_socket, CS buffer+offset, s->len - offset);
677 printf("%sxpected EOF read from client\n",
678 (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
683 while (read(dup_accept_socket, &c, 1) == 1 && c != '\n') ;
691 n = (read(dup_accept_socket, &c, 1) == 1 && c == '.');
692 while (c != '\n' && read(dup_accept_socket, &c, 1) == 1)
695 else if (memcmp(ss, buffer, n) != 0)
697 printf("Comparison failed - bailing out\nExpected: ");
708 if (fgets(CS buffer+offset, sizeof(buffer)-offset, in) == NULL)
710 printf("%sxpected EOF read from client\n",
711 (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
716 n = (int)strlen(CS buffer);
717 while (n > 0 && isspace(buffer[n-1])) n--;
719 printf("%s\n", buffer);
720 if (!data || strcmp(CS buffer, ".") == 0) break;
723 if (strncmp(ss, CS buffer, (int)strlen(ss)) != 0)
725 printf("Comparison failed - bailing out\n");
726 printf("Expected: %s\n", ss);
738 if (s == NULL) printf("End of script\n");
740 if (sockname != NULL) unlink(sockname);
744 /* End of server.c */