summaryrefslogtreecommitdiff
path: root/test/src/server.c
diff options
context:
space:
mode:
Diffstat (limited to 'test/src/server.c')
-rw-r--r--test/src/server.c612
1 files changed, 612 insertions, 0 deletions
diff --git a/test/src/server.c b/test/src/server.c
new file mode 100644
index 000000000..f35d80ba2
--- /dev/null
+++ b/test/src/server.c
@@ -0,0 +1,612 @@
+/* $Cambridge: exim/test/src/server.c,v 1.1 2006/02/06 16:24:05 ph10 Exp $ */
+
+/* A little hacked up program that listens on a given port and allows a script
+to play the part of a remote MTA for testing purposes. This scripted version is
+hacked from my original interactive version. A further hack allows it to listen
+on a Unix domain socket as an alternative to a TCP/IP port.
+
+In an IPv6 world, listening happens on both an IPv6 and an IPv4 socket, always
+on all interfaces, unless the option -noipv6 is given. */
+
+/* ANSI C standard includes */
+
+#include <ctype.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+/* Unix includes */
+
+#include <errno.h>
+#include <dirent.h>
+#include <sys/types.h>
+
+#include <netinet/in_systm.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+
+#ifdef HAVE_NETINET_IP_VAR_H
+#include <netinet/ip_var.h>
+#endif
+
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <utime.h>
+
+#ifdef AF_INET6
+#define HAVE_IPV6 1
+#endif
+
+#ifndef S_ADDR_TYPE
+#define S_ADDR_TYPE u_long
+#endif
+
+
+typedef struct line {
+ struct line *next;
+ char line[1];
+} line;
+
+
+/*************************************************
+* SIGALRM handler - crash out *
+*************************************************/
+
+static void
+sigalrm_handler(int sig)
+{
+sig = sig; /* Keep picky compilers happy */
+printf("\nServer timed out\n");
+exit(99);
+}
+
+
+/*************************************************
+* Get textual IP address *
+*************************************************/
+
+/* This function is copied from Exim */
+
+char *
+host_ntoa(const void *arg, char *buffer)
+{
+char *yield;
+
+/* The new world. It is annoying that we have to fish out the address from
+different places in the block, depending on what kind of address it is. It
+is also a pain that inet_ntop() returns a const char *, whereas the IPv4
+function inet_ntoa() returns just char *, and some picky compilers insist
+on warning if one assigns a const char * to a char *. Hence the casts. */
+
+#if HAVE_IPV6
+char addr_buffer[46];
+int family = ((struct sockaddr *)arg)->sa_family;
+if (family == AF_INET6)
+ {
+ struct sockaddr_in6 *sk = (struct sockaddr_in6 *)arg;
+ yield = (char *)inet_ntop(family, &(sk->sin6_addr), addr_buffer,
+ sizeof(addr_buffer));
+ }
+else
+ {
+ struct sockaddr_in *sk = (struct sockaddr_in *)arg;
+ yield = (char *)inet_ntop(family, &(sk->sin_addr), addr_buffer,
+ sizeof(addr_buffer));
+ }
+
+/* If the result is a mapped IPv4 address, show it in V4 format. */
+
+if (strncmp(yield, "::ffff:", 7) == 0) yield += 7;
+
+#else /* HAVE_IPV6 */
+
+/* The old world */
+
+yield = inet_ntoa(((struct sockaddr_in *)arg)->sin_addr);
+#endif
+
+strcpy(buffer, yield);
+return buffer;
+}
+
+
+/*************************************************
+* Main Program *
+*************************************************/
+
+#define v6n 0 /* IPv6 socket number */
+#define v4n 1 /* IPv4 socket number */
+#define udn 2 /* Unix domain socket number */
+#define skn 2 /* Potential number of sockets */
+
+int main(int argc, char **argv)
+{
+int i;
+int port = 0;
+int listen_socket[3] = { -1, -1, -1 };
+int accept_socket;
+int dup_accept_socket;
+int connection_count = 1;
+int count;
+int on = 1;
+int timeout = 5;
+int use_ipv4 = 1;
+int use_ipv6 = 1;
+int debug = 0;
+int na = 1;
+line *script = NULL;
+line *last = NULL;
+line *s;
+FILE *in, *out;
+
+char *sockname = NULL;
+unsigned char buffer[10240];
+
+struct sockaddr_un sockun; /* don't use "sun" */
+struct sockaddr_un sockun_accepted;
+int sockun_len = sizeof(sockun_accepted);
+
+#if HAVE_IPV6
+struct sockaddr_in6 sin6;
+struct sockaddr_in6 accepted;
+struct in6_addr anyaddr6 = IN6ADDR_ANY_INIT ;
+#else
+struct sockaddr_in accepted;
+#endif
+
+/* Always need an IPv4 structure */
+
+struct sockaddr_in sin4;
+
+int len = sizeof(accepted);
+
+
+/* Sort out the arguments */
+
+while (na < argc && argv[na][0] == '-')
+ {
+ if (strcmp(argv[na], "-d") == 0) debug = 1;
+ else if (strcmp(argv[na], "-t") == 0) timeout = atoi(argv[++na]);
+ else if (strcmp(argv[na], "-noipv4") == 0) use_ipv4 = 0;
+ else if (strcmp(argv[na], "-noipv6") == 0) use_ipv6 = 0;
+ else
+ {
+ printf("server: unknown option %s\n", argv[na]);
+ exit(1);
+ }
+ na++;
+ }
+
+if (!use_ipv4 && !use_ipv6)
+ {
+ printf("server: -noipv4 and -noipv6 cannot both be given\n");
+ exit(1);
+ }
+
+if (na >= argc)
+ {
+ printf("server: no port number or socket name given\n");
+ exit(1);
+ }
+
+if (argv[na][0] == '/')
+ {
+ sockname = argv[na];
+ unlink(sockname); /* in case left lying around */
+ }
+else port = atoi(argv[na]);
+na++;
+
+if (na < argc) connection_count = atoi(argv[na]);
+
+
+/* Create sockets */
+
+if (port == 0) /* Unix domain */
+ {
+ if (debug) printf("Creating Unix domain socket\n");
+ listen_socket[udn] = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (listen_socket[udn] < 0)
+ {
+ printf("Unix domain socket creation failed: %s\n", strerror(errno));
+ exit(1);
+ }
+ }
+else
+ {
+ #if HAVE_IPV6
+ if (use_ipv6)
+ {
+ if (debug) printf("Creating IPv6 socket\n");
+ listen_socket[v6n] = socket(AF_INET6, SOCK_STREAM, 0);
+ if (listen_socket[v6n] < 0)
+ {
+ printf("IPv6 socket creation failed: %s\n", strerror(errno));
+ exit(1);
+ }
+
+ /* If this is an IPv6 wildcard socket, set IPV6_V6ONLY if that option is
+ available. */
+
+ #ifdef IPV6_V6ONLY
+ if (setsockopt(listen_socket[v6n], IPPROTO_IPV6, IPV6_V6ONLY, (char *)(&on),
+ sizeof(on)) < 0)
+ printf("Setting IPV6_V6ONLY on IPv6 wildcard "
+ "socket failed (%s): carrying on without it\n", strerror(errno));
+ #endif /* IPV6_V6ONLY */
+ }
+ #endif /* HAVE_IPV6 */
+
+ /* Create an IPv4 socket if required */
+
+ if (use_ipv4)
+ {
+ if (debug) printf("Creating IPv4 socket\n");
+ listen_socket[v4n] = socket(AF_INET, SOCK_STREAM, 0);
+ if (listen_socket[v4n] < 0)
+ {
+ printf("IPv4 socket creation failed: %s\n", strerror(errno));
+ exit(1);
+ }
+ }
+ }
+
+
+/* Set SO_REUSEADDR on the IP sockets so that the program can be restarted
+while a connection is being handled - this can happen as old connections lie
+around for a bit while crashed processes are tidied away. Without this, a
+connection will prevent reuse of the smtp port for listening. */
+
+for (i = v6n; i <= v4n; i++)
+ {
+ if (listen_socket[i] >= 0 &&
+ setsockopt(listen_socket[i], SOL_SOCKET, SO_REUSEADDR, (char *)(&on),
+ sizeof(on)) < 0)
+ {
+ printf("setting SO_REUSEADDR on socket failed: %s\n", strerror(errno));
+ exit(1);
+ }
+ }
+
+
+/* Now bind the sockets to the required port or path. If a path, ensure
+anyone can write to it. */
+
+if (port == 0)
+ {
+ struct stat statbuf;
+ sockun.sun_family = AF_UNIX;
+ if (debug) printf("Binding Unix domain socket\n");
+ sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1), sockname);
+ if (bind(listen_socket[udn], (struct sockaddr *)&sockun, sizeof(sockun)) < 0)
+ {
+ printf("Unix domain socket bind() failed: %s\n", strerror(errno));
+ exit(1);
+ }
+ (void)stat(sockname, &statbuf);
+ if (debug) printf("Setting Unix domain socket mode: %0x\n",
+ statbuf.st_mode | 0777);
+ if (chmod(sockname, statbuf.st_mode | 0777) < 0)
+ {
+ printf("Unix domain socket chmod() failed: %s\n", strerror(errno));
+ exit(1);
+ }
+ }
+
+else
+ {
+ for (i = 0; i < skn; i++)
+ {
+ if (listen_socket[i] < 0) continue;
+
+ /* For an IPv6 listen, use an IPv6 socket */
+
+ #if HAVE_IPV6
+ if (i == v6n)
+ {
+ memset(&sin6, 0, sizeof(sin6));
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_port = htons(port);
+ sin6.sin6_addr = anyaddr6;
+ if (bind(listen_socket[i], (struct sockaddr *)&sin6, sizeof(sin6)) < 0)
+ {
+ printf("IPv6 socket bind() failed: %s\n", strerror(errno));
+ exit(1);
+ }
+ }
+ else
+ #endif
+
+ /* For an IPv4 bind, use an IPv4 socket, even in an IPv6 world. If an IPv4
+ bind fails EADDRINUSE after IPv6 success, carry on, because it means the
+ IPv6 socket will handle IPv4 connections. */
+
+ {
+ memset(&sin4, 0, sizeof(sin4));
+ sin4.sin_family = AF_INET;
+ sin4.sin_addr.s_addr = (S_ADDR_TYPE)INADDR_ANY;
+ sin4.sin_port = htons(port);
+ if (bind(listen_socket[i], (struct sockaddr *)&sin4, sizeof(sin4)) < 0)
+ {
+ if (listen_socket[v6n] < 0 || errno != EADDRINUSE)
+ {
+ printf("IPv4 socket bind() failed: %s\n", strerror(errno));
+ exit(1);
+ }
+ else
+ {
+ close(listen_socket[i]);
+ listen_socket[i] = -1;
+ }
+ }
+ }
+ }
+ }
+
+
+/* Start listening. If IPv4 fails EADDRINUSE after IPv6 succeeds, ignore the
+error because it means that the IPv6 socket will handle IPv4 connections. Don't
+output anything, because it will mess up the test output, which will be
+different for systems that do this and those that don't. */
+
+for (i = 0; i <= skn; i++)
+ {
+ if (listen_socket[i] >= 0 && listen(listen_socket[i], 5) < 0)
+ {
+ if (i != v4n || listen_socket[v6n] < 0 || errno != EADDRINUSE)
+ {
+ printf("listen() failed: %s\n", strerror(errno));
+ exit(1);
+ }
+ }
+ }
+
+
+/* This program handles only a fixed number of connections, in sequence. Before
+waiting for the first connection, read the standard input, which contains the
+script of things to do. A line containing "++++" is treated as end of file.
+This is so that the Perl driving script doesn't have to close the pipe -
+because that would cause it to wait for this process, which it doesn't yet want
+to do. The driving script adds the "++++" automatically - it doesn't actually
+appear in the test script. */
+
+while (fgets(buffer, sizeof(buffer), stdin) != NULL)
+ {
+ line *next;
+ int n = (int)strlen(buffer);
+ while (n > 0 && isspace(buffer[n-1])) n--;
+ buffer[n] = 0;
+ if (strcmp(buffer, "++++") == 0) break;
+ next = malloc(sizeof(line) + n);
+ next->next = NULL;
+ strcpy(next->line, buffer);
+ if (last == NULL) script = last = next;
+ else last->next = next;
+ last = next;
+ }
+
+fclose(stdin);
+
+/* SIGALRM handler crashes out */
+
+signal(SIGALRM, sigalrm_handler);
+
+/* s points to the current place in the script */
+
+s = script;
+
+for (count = 0; count < connection_count; count++)
+ {
+ alarm(timeout);
+ if (port <= 0)
+ {
+ printf("Listening on %s ... ", sockname);
+ fflush(stdout);
+ accept_socket = accept(listen_socket[udn],
+ (struct sockaddr *)&sockun_accepted, &sockun_len);
+ }
+
+ else
+ {
+ int lcount;
+ int max_socket = 0;
+ fd_set select_listen;
+
+ printf("Listening on port %d ... ", port);
+ fflush(stdout);
+
+ FD_ZERO(&select_listen);
+ for (i = 0; i < skn; i++)
+ {
+ if (listen_socket[i] >= 0) FD_SET(listen_socket[i], &select_listen);
+ if (listen_socket[i] > max_socket) max_socket = listen_socket[i];
+ }
+
+ lcount = select(max_socket + 1, &select_listen, NULL, NULL, NULL);
+ if (lcount < 0)
+ {
+ printf("Select failed\n");
+ fflush(stdout);
+ continue;
+ }
+
+ accept_socket = -1;
+ for (i = 0; i < skn; i++)
+ {
+ if (listen_socket[i] > 0 && FD_ISSET(listen_socket[i], &select_listen))
+ {
+ accept_socket = accept(listen_socket[i],
+ (struct sockaddr *)&accepted, &len);
+ FD_CLR(listen_socket[i], &select_listen);
+ break;
+ }
+ }
+ }
+ alarm(0);
+
+ if (accept_socket < 0)
+ {
+ printf("accept() failed: %s\n", strerror(errno));
+ exit(1);
+ }
+
+ out = fdopen(accept_socket, "w");
+
+ dup_accept_socket = dup(accept_socket);
+
+ if (port > 0)
+ printf("\nConnection request from [%s]\n", host_ntoa(&accepted, buffer));
+ else
+ {
+ printf("\nConnection request\n");
+
+ /* Linux supports a feature for acquiring the peer's credentials, but it
+ appears to be Linux-specific. This code is untested and unused, just
+ saved here for reference. */
+
+ /**********--------------------
+ struct ucred cr;
+ int cl=sizeof(cr);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cr, &cl)==0) {
+ printf("Peer's pid=%d, uid=%d, gid=%d\n",
+ cr.pid, cr.uid, cr.gid);
+ --------------*****************/
+ }
+
+ if (dup_accept_socket < 0)
+ {
+ printf("Couldn't dup socket descriptor\n");
+ printf("421 Connection refused: %s\n", strerror(errno));
+ fprintf(out, "421 Connection refused: %s\r\n", strerror(errno));
+ fclose(out);
+ exit(2);
+ }
+
+ in = fdopen(dup_accept_socket, "r");
+
+ /* Loop for handling the conversation(s). For use in SMTP sessions, there are
+ default rules for determining input and output lines: the latter start with
+ digits. This means that the input looks like SMTP dialog. However, this
+ doesn't work for other tests (e.g. ident tests) so we have explicit '<' and
+ '>' flags for input and output as well as the defaults. */
+
+ for (; s != NULL; s = s->next)
+ {
+ char *ss = s->line;
+
+ /* Output lines either start with '>' or a digit. In the '>' case we can
+ fudge the sending of \r\n as required. Default is \r\n, ">>" send nothing,
+ ">CR>" sends \r only, and ">LF>" sends \n only. We can also force a
+ connection closedown by ">*eof". */
+
+ if (ss[0] == '>')
+ {
+ char *end = "\r\n";
+ printf("%s\n", ss++);
+
+ if (strncmp(ss, "*eof", 4) == 0)
+ {
+ s = s->next;
+ goto END_OFF;
+ }
+
+ if (*ss == '>')
+ { end = ""; ss++; }
+ else if (strncmp(ss, "CR>", 3) == 0)
+ { end = "\r"; ss += 3; }
+ else if (strncmp(ss, "LF>", 3) == 0)
+ { end = "\n"; ss += 3; }
+
+ fprintf(out, "%s%s", ss, end);
+ }
+
+ else if (isdigit((unsigned char)ss[0]))
+ {
+ printf("%s\n", ss);
+ fprintf(out, "%s\r\n", ss);
+ }
+
+ /* If the script line starts with "*sleep" we just sleep for a while
+ before continuing. */
+
+ else if (strncmp(ss, "*sleep ", 7) == 0)
+ {
+ int sleepfor = atoi(ss+7);
+ printf("%s\n", ss);
+ fflush(out);
+ sleep(sleepfor);
+ }
+
+ /* Otherwise the script line is the start of an input line we are expecting
+ from the client, or "*eof" indicating we expect the client to close the
+ connection. Read command line or data lines; the latter are indicated
+ by the expected line being just ".". If the line starts with '<', that
+ doesn't form part of the expected input. (This allows for incoming data
+ starting with a digit.) */
+
+ else
+ {
+ int offset;
+ int data = strcmp(ss, ".") == 0;
+
+ if (ss[0] == '<')
+ {
+ buffer[0] = '<';
+ offset = 1;
+ }
+ else offset = 0;
+
+ fflush(out);
+
+ for (;;)
+ {
+ int n;
+ alarm(timeout);
+ if (fgets(buffer+offset, sizeof(buffer)-offset, in) == NULL)
+ {
+ printf("%sxpected EOF read from client\n",
+ (strncmp(ss, "*eof", 4) == 0)? "E" : "Une");
+ s = s->next;
+ goto END_OFF;
+ }
+ alarm(0);
+ n = (int)strlen(buffer);
+ while (n > 0 && isspace(buffer[n-1])) n--;
+ buffer[n] = 0;
+ printf("%s\n", buffer);
+ if (!data || strcmp(buffer, ".") == 0) break;
+ }
+
+ if (strncmp(ss, buffer, (int)strlen(ss)) != 0)
+ {
+ printf("Comparison failed - bailing out\n");
+ printf("Expected: %s\n", ss);
+ break;
+ }
+ }
+ }
+
+ END_OFF:
+ fclose(in);
+ fclose(out);
+ }
+
+if (s == NULL) printf("End of script\n");
+
+if (sockname != NULL) unlink(sockname);
+exit(0);
+}
+
+/* End of server.c */