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 *************************************************/
71 sigalrm_handler(int sig)
73 sig = sig; /* Keep picky compilers happy */
74 printf("\nServer timed out\n");
75 exit(tmo_noerror ? 0 : 99);
79 /*************************************************
80 * Get textual IP address *
81 *************************************************/
83 /* This function is copied from Exim */
86 host_ntoa(const void *arg, char *buffer)
90 /* The new world. It is annoying that we have to fish out the address from
91 different places in the block, depending on what kind of address it is. It
92 is also a pain that inet_ntop() returns a const char *, whereas the IPv4
93 function inet_ntoa() returns just char *, and some picky compilers insist
94 on warning if one assigns a const char * to a char *. Hence the casts. */
98 int family = ((struct sockaddr *)arg)->sa_family;
99 if (family == AF_INET6)
101 struct sockaddr_in6 *sk = (struct sockaddr_in6 *)arg;
102 yield = (char *)inet_ntop(family, &(sk->sin6_addr), addr_buffer,
103 sizeof(addr_buffer));
107 struct sockaddr_in *sk = (struct sockaddr_in *)arg;
108 yield = (char *)inet_ntop(family, &(sk->sin_addr), addr_buffer,
109 sizeof(addr_buffer));
112 /* If the result is a mapped IPv4 address, show it in V4 format. */
114 if (strncmp(yield, "::ffff:", 7) == 0) yield += 7;
116 #else /* HAVE_IPV6 */
120 yield = inet_ntoa(((struct sockaddr_in *)arg)->sin_addr);
123 strcpy(buffer, yield);
130 printit(char * s, int n)
134 unsigned char c = *s++;
137 else if (c >= ' ' && c <= '~') /* assumes ascii */
140 printf("\\x%02x", c);
147 /*************************************************
149 *************************************************/
151 #define v6n 0 /* IPv6 socket number */
152 #define v4n 1 /* IPv4 socket number */
153 #define udn 2 /* Unix domain socket number */
154 #define skn 2 /* Potential number of sockets */
156 int main(int argc, char **argv)
160 int listen_socket[3] = { -1, -1, -1 };
162 int dup_accept_socket;
163 int connection_count = 1;
167 int initial_pause = 0;
177 char *pidfile = NULL;
179 char *sockname = NULL;
180 unsigned char buffer[10240];
182 struct sockaddr_un sockun; /* don't use "sun" */
183 struct sockaddr_un sockun_accepted;
184 int sockun_len = sizeof(sockun_accepted);
187 struct sockaddr_in6 sin6;
188 struct sockaddr_in6 accepted;
189 struct in6_addr anyaddr6 = IN6ADDR_ANY_INIT ;
191 struct sockaddr_in accepted;
194 /* Always need an IPv4 structure */
196 struct sockaddr_in sin4;
198 int len = sizeof(accepted);
201 /* Sort out the arguments */
202 if (argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")))
204 printf("Usage: %s [options] port|socket [connection count]\n", argv[0]);
207 "\n\t-i n n seconds initial delay"
208 "\n\t-noipv4 disable ipv4"
209 "\n\t-noipv6 disable ipv6"
210 "\n\t-oP file write PID to file"
211 "\n\t-t n n seconds timeout"
216 while (na < argc && argv[na][0] == '-')
218 if (strcmp(argv[na], "-d") == 0) debug = 1;
219 else if (strcmp(argv[na], "-t") == 0)
221 if (tmo_noerror = ((timeout = atoi(argv[++na])) < 0)) timeout = -timeout;
223 else if (strcmp(argv[na], "-i") == 0) initial_pause = atoi(argv[++na]);
224 else if (strcmp(argv[na], "-noipv4") == 0) use_ipv4 = 0;
225 else if (strcmp(argv[na], "-noipv6") == 0) use_ipv6 = 0;
226 else if (strcmp(argv[na], "-oP") == 0) pidfile = argv[++na];
229 printf("server: unknown option %s, try -h or --help\n", argv[na]);
235 if (!use_ipv4 && !use_ipv6)
237 printf("server: -noipv4 and -noipv6 cannot both be given\n");
243 printf("server: no port number or socket name given\n");
247 if (argv[na][0] == '/')
250 unlink(sockname); /* in case left lying around */
252 else port = atoi(argv[na]);
255 if (na < argc) connection_count = atoi(argv[na]);
258 /* Initial pause (before creating listen sockets */
259 if (initial_pause > 0)
262 printf("%d: Inital pause of %d seconds\n", time(NULL), initial_pause);
264 printf("Inital pause of %d seconds\n", initial_pause);
265 while (initial_pause > 0)
266 initial_pause = sleep(initial_pause);
271 if (port == 0) /* Unix domain */
273 if (debug) printf("%d: Creating Unix domain socket\n", time(NULL));
274 listen_socket[udn] = socket(PF_UNIX, SOCK_STREAM, 0);
275 if (listen_socket[udn] < 0)
277 printf("Unix domain socket creation failed: %s\n", strerror(errno));
286 if (debug) printf("Creating IPv6 socket\n");
287 listen_socket[v6n] = socket(AF_INET6, SOCK_STREAM, 0);
288 if (listen_socket[v6n] < 0)
290 printf("IPv6 socket creation failed: %s\n", strerror(errno));
294 /* If this is an IPv6 wildcard socket, set IPV6_V6ONLY if that option is
298 if (setsockopt(listen_socket[v6n], IPPROTO_IPV6, IPV6_V6ONLY, (char *)(&on),
300 printf("Setting IPV6_V6ONLY on IPv6 wildcard "
301 "socket failed (%s): carrying on without it\n", strerror(errno));
302 #endif /* IPV6_V6ONLY */
304 #endif /* HAVE_IPV6 */
306 /* Create an IPv4 socket if required */
310 if (debug) printf("Creating IPv4 socket\n");
311 listen_socket[v4n] = socket(AF_INET, SOCK_STREAM, 0);
312 if (listen_socket[v4n] < 0)
314 printf("IPv4 socket creation failed: %s\n", strerror(errno));
321 /* Set SO_REUSEADDR on the IP sockets so that the program can be restarted
322 while a connection is being handled - this can happen as old connections lie
323 around for a bit while crashed processes are tidied away. Without this, a
324 connection will prevent reuse of the smtp port for listening. */
326 for (i = v6n; i <= v4n; i++)
328 if (listen_socket[i] >= 0 &&
329 setsockopt(listen_socket[i], SOL_SOCKET, SO_REUSEADDR, (char *)(&on),
332 printf("setting SO_REUSEADDR on socket failed: %s\n", strerror(errno));
338 /* Now bind the sockets to the required port or path. If a path, ensure
339 anyone can write to it. */
344 sockun.sun_family = AF_UNIX;
345 if (debug) printf("Binding Unix domain socket\n");
346 sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1), sockname);
347 if (bind(listen_socket[udn], (struct sockaddr *)&sockun, sizeof(sockun)) < 0)
349 printf("Unix domain socket bind() failed: %s\n", strerror(errno));
352 (void)stat(sockname, &statbuf);
353 if (debug) printf("Setting Unix domain socket mode: %0x\n",
354 statbuf.st_mode | 0777);
355 if (chmod(sockname, statbuf.st_mode | 0777) < 0)
357 printf("Unix domain socket chmod() failed: %s\n", strerror(errno));
364 for (i = 0; i < skn; i++)
366 if (listen_socket[i] < 0) continue;
368 /* For an IPv6 listen, use an IPv6 socket */
373 memset(&sin6, 0, sizeof(sin6));
374 sin6.sin6_family = AF_INET6;
375 sin6.sin6_port = htons(port);
376 sin6.sin6_addr = anyaddr6;
377 if (bind(listen_socket[i], (struct sockaddr *)&sin6, sizeof(sin6)) < 0)
379 printf("IPv6 socket bind() failed: %s\n", strerror(errno));
386 /* For an IPv4 bind, use an IPv4 socket, even in an IPv6 world. If an IPv4
387 bind fails EADDRINUSE after IPv6 success, carry on, because it means the
388 IPv6 socket will handle IPv4 connections. */
391 memset(&sin4, 0, sizeof(sin4));
392 sin4.sin_family = AF_INET;
393 sin4.sin_addr.s_addr = (S_ADDR_TYPE)INADDR_ANY;
394 sin4.sin_port = htons(port);
395 if (bind(listen_socket[i], (struct sockaddr *)&sin4, sizeof(sin4)) < 0)
397 if (listen_socket[v6n] < 0 || errno != EADDRINUSE)
399 printf("IPv4 socket bind() failed: %s\n", strerror(errno));
404 close(listen_socket[i]);
405 listen_socket[i] = -1;
413 /* Start listening. If IPv4 fails EADDRINUSE after IPv6 succeeds, ignore the
414 error because it means that the IPv6 socket will handle IPv4 connections. Don't
415 output anything, because it will mess up the test output, which will be
416 different for systems that do this and those that don't. */
418 for (i = 0; i <= skn; i++)
420 if (listen_socket[i] >= 0 && listen(listen_socket[i], 5) < 0)
422 if (i != v4n || listen_socket[v6n] < 0 || errno != EADDRINUSE)
424 printf("listen() failed: %s\n", strerror(errno));
434 if (!(p = fopen(pidfile, "w")))
436 fprintf(stderr, "pidfile create failed: %s\n", strerror(errno));
439 fprintf(p, "%ld\n", (long)getpid());
443 /* This program handles only a fixed number of connections, in sequence. Before
444 waiting for the first connection, read the standard input, which contains the
445 script of things to do. A line containing "++++" is treated as end of file.
446 This is so that the Perl driving script doesn't have to close the pipe -
447 because that would cause it to wait for this process, which it doesn't yet want
448 to do. The driving script adds the "++++" automatically - it doesn't actually
449 appear in the test script. Within lines we interpret \xNN and \\ groups */
451 while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
455 int n = (int)strlen(CS buffer);
457 if (n > 1 && buffer[0] == '>' && buffer[1] == '>')
459 while (n > 0 && isspace(buffer[n-1])) n--;
461 if (strcmp(CS buffer, "++++") == 0) break;
462 next = malloc(sizeof(line) + n);
466 char * s = CS buffer;
471 if (cl == '\\' && (cl = *++s) == 'x')
473 if ((ch = *++s - '0') > 9 && (ch -= 'A'-'9'-1) > 15) ch -= 'a'-'A';
474 if ((cl = *++s - '0') > 9 && (cl -= 'A'-'9'-1) > 15) cl -= 'a'-'A';
481 next->len = d - next->line - 1;
482 if (last == NULL) script = last = next;
483 else last->next = next;
489 /* SIGALRM handler crashes out */
491 signal(SIGALRM, sigalrm_handler);
493 /* s points to the current place in the script */
497 for (count = 0; count < connection_count; count++)
502 printf("Listening on %s ... ", sockname);
504 accept_socket = accept(listen_socket[udn],
505 (struct sockaddr *)&sockun_accepted, &sockun_len);
512 fd_set select_listen;
514 printf("Listening on port %d ... ", port);
517 FD_ZERO(&select_listen);
518 for (i = 0; i < skn; i++)
520 if (listen_socket[i] >= 0) FD_SET(listen_socket[i], &select_listen);
521 if (listen_socket[i] > max_socket) max_socket = listen_socket[i];
524 lcount = select(max_socket + 1, &select_listen, NULL, NULL, NULL);
527 printf("Select failed\n");
533 for (i = 0; i < skn; i++)
535 if (listen_socket[i] > 0 && FD_ISSET(listen_socket[i], &select_listen))
537 accept_socket = accept(listen_socket[i],
538 (struct sockaddr *)&accepted, &len);
539 FD_CLR(listen_socket[i], &select_listen);
546 if (accept_socket < 0)
548 printf("accept() failed: %s\n", strerror(errno));
552 out = fdopen(accept_socket, "w");
554 dup_accept_socket = dup(accept_socket);
557 printf("\nConnection request from [%s]\n", host_ntoa(&accepted, CS buffer));
560 printf("\nConnection request\n");
562 /* Linux supports a feature for acquiring the peer's credentials, but it
563 appears to be Linux-specific. This code is untested and unused, just
564 saved here for reference. */
566 /**********--------------------
570 if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl)==0) {
571 printf("Peer's pid=%d, uid=%d, gid=%d\n",
572 cr.pid, cr.uid, cr.gid);
573 --------------*****************/
577 if (dup_accept_socket < 0)
579 printf("Couldn't dup socket descriptor\n");
580 printf("421 Connection refused: %s\n", strerror(errno));
581 fprintf(out, "421 Connection refused: %s\r\n", strerror(errno));
586 in = fdopen(dup_accept_socket, "r");
588 /* Loop for handling the conversation(s). For use in SMTP sessions, there are
589 default rules for determining input and output lines: the latter start with
590 digits. This means that the input looks like SMTP dialog. However, this
591 doesn't work for other tests (e.g. ident tests) so we have explicit '<' and
592 '>' flags for input and output as well as the defaults. */
594 for (; s != NULL; s = s->next)
598 /* Output lines either start with '>' or a digit. In the '>' case we can
599 fudge the sending of \r\n as required. Default is \r\n, ">>" send nothing,
600 ">CR>" sends \r only, and ">LF>" sends \n only. We can also force a
601 connection closedown by ">*eof". */
606 unsigned len = s->len;
607 printit(ss++, len--);
609 if (strncmp(ss, "*eof", 4) == 0)
616 { end = ""; ss++; len--; }
617 else if (strncmp(ss, "CR>", 3) == 0)
618 { end = "\r"; ss += 3; len -= 3; }
619 else if (strncmp(ss, "LF>", 3) == 0)
620 { end = "\n"; ss += 3; len -= 3; }
622 fwrite(ss, 1, len, out);
623 if (*end) fprintf(out, end);
626 else if (isdigit((unsigned char)ss[0]))
629 fprintf(out, "%s\r\n", ss);
632 /* If the script line starts with "*sleep" we just sleep for a while
633 before continuing. */
635 else if (strncmp(ss, "*sleep ", 7) == 0)
637 int sleepfor = atoi(ss+7);
643 /* Otherwise the script line is the start of an input line we are expecting
644 from the client, or "*eof" indicating we expect the client to close the
645 connection. Read command line or data lines; the latter are indicated
646 by the expected line being just ".". If the line starts with '<', that
647 doesn't form part of the expected input. (This allows for incoming data
648 starting with a digit.) If the line starts with '<<' we operate in
649 unbuffered rather than line mode and assume that a single read gets the
655 int data = strcmp(ss, ".") == 0;
679 n = read(dup_accept_socket, CS buffer+offset, s->len - offset);
682 printf("%sxpected EOF read from client\n",
683 (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
688 while (read(dup_accept_socket, &c, 1) == 1 && c != '\n') ;
696 n = (read(dup_accept_socket, &c, 1) == 1 && c == '.');
697 while (c != '\n' && read(dup_accept_socket, &c, 1) == 1)
700 else if (memcmp(ss, buffer, n) != 0)
702 printf("Comparison failed - bailing out\nExpected: ");
713 if (fgets(CS buffer+offset, sizeof(buffer)-offset, in) == NULL)
715 printf("%sxpected EOF read from client\n",
716 (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
721 n = (int)strlen(CS buffer);
722 while (n > 0 && isspace(buffer[n-1])) n--;
724 printf("%s\n", buffer);
725 if (!data || strcmp(CS buffer, ".") == 0) break;
728 if (strncmp(ss, CS buffer, (int)strlen(ss)) != 0)
730 printf("Comparison failed - bailing out\n");
731 printf("Expected: %s\n", ss);
743 if (s == NULL) printf("End of script\n");
745 if (sockname != NULL) unlink(sockname);
749 /* End of server.c */