diff options
Diffstat (limited to 'test/src/client.c')
-rw-r--r-- | test/src/client.c | 898 |
1 files changed, 898 insertions, 0 deletions
diff --git a/test/src/client.c b/test/src/client.c new file mode 100644 index 000000000..0a2159472 --- /dev/null +++ b/test/src/client.c @@ -0,0 +1,898 @@ +/* $Cambridge: exim/test/src/client.c,v 1.1 2006/02/06 16:24:05 ph10 Exp $ */ + +/* A little hacked up program that makes a TCP/IP call and reads a script to +drive it, for testing Exim server code running as a daemon. It's got a bit +messy with the addition of support for either OpenSSL or GnuTLS. The code for +those was hacked out of Exim itself. */ + +/* 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> + +#include <netdb.h> +#include <arpa/inet.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/socket.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 unsigned char uschar; + +#define CS (char *) +#define US (unsigned char *) + +#define FALSE 0 +#define TRUE 1 + + + +static int sigalrm_seen = 0; + + +/* TLS support can be optionally included, either for OpenSSL or GnuTLS. The +latter needs a whole pile of tables. */ + +#ifdef HAVE_OPENSSL +#define HAVE_TLS +#include <openssl/crypto.h> +#include <openssl/x509.h> +#include <openssl/pem.h> +#include <openssl/ssl.h> +#include <openssl/err.h> +#include <openssl/rand.h> +#endif + + +#ifdef HAVE_GNUTLS +#define HAVE_TLS +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> + +#define DH_BITS 768 +#define RSA_BITS 512 + +/* Local static variables for GNUTLS */ + +static gnutls_rsa_params rsa_params = NULL; +static gnutls_dh_params dh_params = NULL; + +static gnutls_certificate_credentials_t x509_cred = NULL; +static gnutls_session tls_session = NULL; + +static int ssl_session_timeout = 200; + +/* Priorities for TLS algorithms to use. */ + +static const int protocol_priority[16] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 }; + +static const int kx_priority[16] = { + GNUTLS_KX_RSA, + GNUTLS_KX_DHE_DSS, + GNUTLS_KX_DHE_RSA, + GNUTLS_KX_RSA_EXPORT, + 0 }; + +static int default_cipher_priority[16] = { + GNUTLS_CIPHER_AES_256_CBC, + GNUTLS_CIPHER_AES_128_CBC, + GNUTLS_CIPHER_3DES_CBC, + GNUTLS_CIPHER_ARCFOUR_128, + 0 }; + +static const int mac_priority[16] = { + GNUTLS_MAC_SHA, + GNUTLS_MAC_MD5, + 0 }; + +static const int comp_priority[16] = { GNUTLS_COMP_NULL, 0 }; +static const int cert_type_priority[16] = { GNUTLS_CRT_X509, 0 }; + +#endif + + + + +/************************************************* +* SIGALRM handler - crash out * +*************************************************/ + +static void +sigalrm_handler_crash(int sig) +{ +sig = sig; /* Keep picky compilers happy */ +printf("\nClient timed out\n"); +exit(99); +} + + +/************************************************* +* SIGALRM handler - set flag * +*************************************************/ + +static void +sigalrm_handler_flag(int sig) +{ +sig = sig; /* Keep picky compilers happy */ +sigalrm_seen = 1; +} + + + +/****************************************************************************/ +/****************************************************************************/ + +#ifdef HAVE_OPENSSL +/************************************************* +* Start an OpenSSL TLS session * +*************************************************/ + +int tls_start(int sock, SSL **ssl, SSL_CTX *ctx) +{ +int rc; +static const char *sid_ctx = "exim"; + +RAND_load_file("client.c", -1); /* Not *very* random! */ + +*ssl = SSL_new (ctx); +SSL_set_session_id_context(*ssl, sid_ctx, strlen(sid_ctx)); +SSL_set_fd (*ssl, sock); +SSL_set_connect_state(*ssl); + +signal(SIGALRM, sigalrm_handler_flag); +sigalrm_seen = 0; +alarm(5); +rc = SSL_connect (*ssl); +alarm(0); + +if (sigalrm_seen) + { + printf("SSL_connect timed out\n"); + return 0; + } + +if (rc <= 0) + { + ERR_print_errors_fp(stdout); + return 0; + } + +printf("SSL connection using %s\n", SSL_get_cipher (*ssl)); +return 1; +} + + +/************************************************* +* SSL Information callback * +*************************************************/ + +static void +info_callback(SSL *s, int where, int ret) +{ +where = where; +ret = ret; +printf("SSL info: %s\n", SSL_state_string_long(s)); +} +#endif + + +/****************************************************************************/ +/****************************************************************************/ + + +#ifdef HAVE_GNUTLS +/************************************************* +* Handle GnuTLS error * +*************************************************/ + +/* Called from lots of places when errors occur before actually starting to do +the TLS handshake, that is, while the session is still in clear. + +Argument: + prefix prefix text + err a GnuTLS error number, or 0 if local error + +Returns: doesn't - it dies +*/ + +static void +gnutls_error(uschar *prefix, int err) +{ +fprintf(stderr, "GnuTLS connection error:%s", prefix); +if (err != 0) fprintf(stderr, " %s", gnutls_strerror(err)); +fprintf(stderr, "\n"); +exit(1); +} + + + +/************************************************* +* Setup up RSA and DH parameters * +*************************************************/ + +/* For the test suite, the parameters should always be available in the spool +directory. */ + +static void +init_rsa_dh(void) +{ +int fd; +int ret; +gnutls_datum m; +uschar filename[200]; +struct stat statbuf; + +/* Initialize the data structures for holding the parameters */ + +ret = gnutls_rsa_params_init(&rsa_params); +if (ret < 0) gnutls_error(US"init rsa_params", ret); + +ret = gnutls_dh_params_init(&dh_params); +if (ret < 0) gnutls_error(US"init dh_params", ret); + +/* Open the cache file for reading and if successful, read it and set up the +parameters. If we can't set up the RSA parameters, assume that we are dealing +with an old-style cache file that is in another format, and fall through to +compute new values. However, if we correctly get RSA parameters, a failure to +set up D-H parameters is treated as an error. */ + +fd = open("aux-fixed/gnutls-params", O_RDONLY, 0); +if (fd < 0) + { + fprintf(stderr, "Failed to open spool/gnutls-params: %s\n", strerror(errno)); + exit(1); + } + +if (fstat(fd, &statbuf) < 0) + { + (void)close(fd); + return gnutls_error(US"TLS cache stat failed", 0); + } + +m.size = statbuf.st_size; +m.data = malloc(m.size); +if (m.data == NULL) + return gnutls_error(US"memory allocation failed", 0); +if (read(fd, m.data, m.size) != m.size) + return gnutls_error(US"TLS cache read failed", 0); +(void)close(fd); + +ret = gnutls_rsa_params_import_pkcs1(rsa_params, &m, GNUTLS_X509_FMT_PEM); +if (ret < 0) return gnutls_error(US"RSA params import", ret); +ret = gnutls_dh_params_import_pkcs3(dh_params, &m, GNUTLS_X509_FMT_PEM); +if (ret < 0) return gnutls_error(US"DH params import", ret); +free(m.data); +} + + + + +/************************************************* +* Initialize for GnuTLS * +*************************************************/ + +/* +Arguments: + certificate certificate file + privatekey private key file +*/ + +static void +tls_init(uschar *certificate, uschar *privatekey) +{ +int rc; + +rc = gnutls_global_init(); +if (rc < 0) gnutls_error(US"gnutls_global_init", rc); + +/* Read RSA and D-H parameters from the cache file. */ + +init_rsa_dh(); + +/* Create the credentials structure */ + +rc = gnutls_certificate_allocate_credentials(&x509_cred); +if (rc < 0) gnutls_error(US"certificate_allocate_credentials", rc); + +/* Set the certificate and private keys */ + +if (certificate != NULL) + { + rc = gnutls_certificate_set_x509_key_file(x509_cred, CS certificate, + CS privatekey, GNUTLS_X509_FMT_PEM); + if (rc < 0) gnutls_error("gnutls_certificate", rc); + } + +/* Associate the parameters with the x509 credentials structure. */ + +gnutls_certificate_set_dh_params(x509_cred, dh_params); +gnutls_certificate_set_rsa_export_params(x509_cred, rsa_params); +} + + + +/************************************************* +* Initialize a single GNUTLS session * +*************************************************/ + +static gnutls_session +tls_session_init(void) +{ +gnutls_session session; + +gnutls_init(&session, GNUTLS_CLIENT); + +gnutls_cipher_set_priority(session, default_cipher_priority); +gnutls_compression_set_priority(session, comp_priority); +gnutls_kx_set_priority(session, kx_priority); +gnutls_protocol_set_priority(session, protocol_priority); +gnutls_mac_set_priority(session, mac_priority); + +gnutls_cred_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred); + +gnutls_dh_set_prime_bits(session, DH_BITS); +gnutls_db_set_cache_expiration(session, ssl_session_timeout); + +return session; +} +#endif + + +/****************************************************************************/ +/****************************************************************************/ + + + + +/************************************************* +* Main Program * +*************************************************/ + +/* Usage: client + <IP address> + <port> + [<outgoing interface>] + [<cert file>] + [<key file>] +*/ + +int main(int argc, char **argv) +{ +struct sockaddr *s_ptr; +struct sockaddr_in s_in4; +char *interface = NULL; +char *address = NULL; +char *certfile = NULL; +char *keyfile = NULL; +int argi = 1; +int host_af, port, s_len, rc, sock, save_errno; +int timeout = 1; +int tls_active = 0; +int sent_starttls = 0; +int tls_on_connect = 0; + +#if HAVE_IPV6 +struct sockaddr_in6 s_in6; +#endif + +#ifdef HAVE_OPENSSL +SSL_CTX* ctx; +SSL* ssl; +#endif + +unsigned char outbuffer[10240]; +unsigned char inbuffer[10240]; +unsigned char *inptr = inbuffer; + +*inptr = 0; /* Buffer empty */ + +/* Options */ + +while (argc >= argi + 1 && argv[argi][0] == '-') + { + if (strcmp(argv[argi], "-tls-on-connect") == 0) + { + tls_on_connect = 1; + argi++; + } + else if (argv[argi][1] == 't' && isdigit(argv[argi][2])) + { + timeout = atoi(argv[argi]+1); + argi++; + } + else + { + printf("Unrecognized option %s\n", argv[argi]); + exit(1); + } + } + +/* Mandatory 1st arg is IP address */ + +if (argc < argi+1) + { + printf("No IP address given\n"); + exit(1); + } + +address = argv[argi++]; +host_af = (strchr(address, ':') != NULL)? AF_INET6 : AF_INET; + +/* Mandatory 2nd arg is port */ + +if (argc < argi+1) + { + printf("No port number given\n"); + exit(1); + } + +port = atoi(argv[argi++]); + +/* Optional next arg is interface */ + +if (argc > argi && + (isdigit((unsigned char)argv[argi][0]) || argv[argi][0] == ':')) + interface = argv[argi++]; + +/* Any more arguments are the name of a certificate file and key file */ + +if (argc > argi) certfile = argv[argi++]; +if (argc > argi) keyfile = argv[argi++]; + + +#if HAVE_IPV6 +/* For an IPv6 address, use an IPv6 sockaddr structure. */ + +if (host_af == AF_INET6) + { + s_ptr = (struct sockaddr *)&s_in6; + s_len = sizeof(s_in6); + } +else +#endif + +/* For an IPv4 address, use an IPv4 sockaddr structure, +even on an IPv6 system. */ + + { + s_ptr = (struct sockaddr *)&s_in4; + s_len = sizeof(s_in4); + } + +printf("Connecting to %s port %d ... ", address, port); + +sock = socket(host_af, SOCK_STREAM, 0); +if (sock < 0) + { + printf("socket creation failed: %s\n", strerror(errno)); + exit(1); + } + +/* Bind to a specific interface if requested. On an IPv6 system, this has +to be of the same family as the address we are calling. On an IPv4 system the +test is redundant, but it keeps the code tidier. */ + +if (interface != NULL) + { + int interface_af = (strchr(interface, ':') != NULL)? AF_INET6 : AF_INET; + + if (interface_af == host_af) + { + #if HAVE_IPV6 + + /* Set up for IPv6 binding */ + + if (host_af == AF_INET6) + { + memset(&s_in6, 0, sizeof(s_in6)); + s_in6.sin6_family = AF_INET6; + s_in6.sin6_port = 0; + if (inet_pton(AF_INET6, interface, &s_in6.sin6_addr) != 1) + { + printf("Unable to parse \"%s\"", interface); + exit(1); + } + } + else + #endif + + /* Set up for IPv4 binding */ + + { + memset(&s_in4, 0, sizeof(s_in4)); + s_in4.sin_family = AF_INET; + s_in4.sin_port = 0; + s_in4.sin_addr.s_addr = (S_ADDR_TYPE)inet_addr(interface); + } + + /* Bind */ + + if (bind(sock, s_ptr, s_len) < 0) + { + printf("Unable to bind outgoing SMTP call to %s: %s", + interface, strerror(errno)); + exit(1); + } + } + } + +/* Set up a remote IPv6 address */ + +#if HAVE_IPV6 +if (host_af == AF_INET6) + { + memset(&s_in6, 0, sizeof(s_in6)); + s_in6.sin6_family = AF_INET6; + s_in6.sin6_port = htons(port); + if (inet_pton(host_af, address, &s_in6.sin6_addr) != 1) + { + printf("Unable to parse \"%s\"", address); + exit(1); + } + } +else +#endif + +/* Set up a remote IPv4 address */ + + { + memset(&s_in4, 0, sizeof(s_in4)); + s_in4.sin_family = AF_INET; + s_in4.sin_port = htons(port); + s_in4.sin_addr.s_addr = (S_ADDR_TYPE)inet_addr(address); + } + +/* SIGALRM handler crashes out */ + +signal(SIGALRM, sigalrm_handler_crash); +alarm(timeout); +rc = connect(sock, s_ptr, s_len); +save_errno = errno; +alarm(0); + +/* A failure whose error code is "Interrupted system call" is in fact +an externally applied timeout if the signal handler has been run. */ + +if (rc < 0) + { + close(sock); + printf("failed: %s\n", strerror(save_errno)); + exit(1); + } + +printf("connected\n"); + + +/* --------------- Set up for OpenSSL --------------- */ + +#ifdef HAVE_OPENSSL +SSL_library_init(); +SSL_load_error_strings(); + +ctx = SSL_CTX_new(SSLv23_method()); +if (ctx == NULL) + { + printf ("SSL_CTX_new failed\n"); + exit(1); + } + +if (certfile != NULL) + { + if (!SSL_CTX_use_certificate_file(ctx, certfile, SSL_FILETYPE_PEM)) + { + printf("SSL_CTX_use_certificate_file failed\n"); + exit(1); + } + printf("Certificate file = %s\n", certfile); + } + +if (keyfile != NULL) + { + if (!SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM)) + { + printf("SSL_CTX_use_PrivateKey_file failed\n"); + exit(1); + } + printf("Key file = %s\n", keyfile); + } + +SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_BOTH); +SSL_CTX_set_timeout(ctx, 200); +SSL_CTX_set_info_callback(ctx, (void (*)())info_callback); +#endif + + +/* --------------- Set up for GnuTLS --------------- */ + +#ifdef HAVE_GNUTLS +if (certfile != NULL) printf("Certificate file = %s\n", certfile); +if (keyfile != NULL) printf("Key file = %s\n", keyfile); +tls_init(certfile, keyfile); +tls_session = tls_session_init(); +gnutls_transport_set_ptr(tls_session, (gnutls_transport_ptr)sock); + +/* When the server asks for a certificate and the client does not have one, +there is a SIGPIPE error in the gnutls_handshake() function for some reason +that is not understood. As luck would have it, this has never hit Exim itself +because it ignores SIGPIPE errors. Doing the same here allows it all to work as +one wants. */ + +signal(SIGPIPE, SIG_IGN); +#endif + +/* ---------------------------------------------- */ + + +/* Start TLS session if configured to do so without STARTTLS */ + +#ifdef HAVE_TLS +if (tls_on_connect) + { + printf("Attempting to start TLS\n"); + + #ifdef HAVE_OPENSSL + tls_active = tls_start(sock, &ssl, ctx); + #endif + + #ifdef HAVE_GNUTLS + sigalrm_seen = FALSE; + alarm(timeout); + tls_active = gnutls_handshake(tls_session) >= 0; + alarm(0); + #endif + + if (!tls_active) + printf("Failed to start TLS\n"); + else + printf("Succeeded in starting TLS\n"); + } +#endif + +while (fgets(outbuffer, sizeof(outbuffer), stdin) != NULL) + { + int n = (int)strlen(outbuffer); + while (n > 0 && isspace(outbuffer[n-1])) n--; + outbuffer[n] = 0; + + /* Expect incoming */ + + if (strncmp(outbuffer, "??? ", 4) == 0) + { + unsigned char *lineptr; + printf("%s\n", outbuffer); + + if (*inptr == 0) /* Refill input buffer */ + { + if (tls_active) + { + #ifdef HAVE_OPENSSL + rc = SSL_read (ssl, inbuffer, sizeof(inbuffer) - 1); + #endif + #ifdef HAVE_GNUTLS + rc = gnutls_record_recv(tls_session, CS inbuffer, sizeof(inbuffer) - 1); + #endif + } + else + { + alarm(timeout); + rc = read(sock, inbuffer, sizeof(inbuffer)); + alarm(0); + } + + if (rc < 0) + { + printf("Read error %s\n", strerror(errno)); + exit(1) ; + } + else if (rc == 0) + { + printf("Unexpected EOF read\n"); + close(sock); + exit(1); + } + else + { + inbuffer[rc] = 0; + inptr = inbuffer; + } + } + + lineptr = inptr; + while (*inptr != 0 && *inptr != '\r' && *inptr != '\n') inptr++; + if (*inptr != 0) + { + *inptr++ = 0; + if (*inptr == '\n') inptr++; + } + + printf("<<< %s\n", lineptr); + if (strncmp(lineptr, outbuffer + 4, (int)strlen(outbuffer) - 4) != 0) + { + printf("\n******** Input mismatch ********\n"); + exit(1); + } + + #ifdef HAVE_TLS + if (sent_starttls) + { + if (lineptr[0] == '2') + { + printf("Attempting to start TLS\n"); + fflush(stdout); + + #ifdef HAVE_OPENSSL + tls_active = tls_start(sock, &ssl, ctx); + #endif + + #ifdef HAVE_GNUTLS + sigalrm_seen = FALSE; + alarm(timeout); + tls_active = gnutls_handshake(tls_session) >= 0; + alarm(0); + #endif + + if (!tls_active) + { + printf("Failed to start TLS\n"); + fflush(stdout); + } + else + printf("Succeeded in starting TLS\n"); + } + else printf("Abandoning TLS start attempt\n"); + } + sent_starttls = 0; + #endif + } + + /* Wait for a bit before proceeding */ + + else if (strncmp(outbuffer, "+++ ", 4) == 0) + { + printf("%s\n", outbuffer); + sleep(atoi(outbuffer + 4)); + } + + /* Send outgoing, but barf if unconsumed incoming */ + + else + { + unsigned char *escape; + + if (*inptr != 0) + { + printf("Unconsumed input: %s", inptr); + printf(" About to send: %s\n", outbuffer); + exit(1); + } + + #ifdef HAVE_TLS + + /* Shutdown TLS */ + + if (strcmp(outbuffer, "stoptls") == 0 || + strcmp(outbuffer, "STOPTLS") == 0) + { + if (!tls_active) + { + printf("STOPTLS read when TLS not active\n"); + exit(1); + } + printf("Shutting down TLS encryption\n"); + + #ifdef HAVE_OPENSSL + SSL_shutdown(ssl); + SSL_free(ssl); + #endif + + #ifdef HAVE_GNUTLS + gnutls_bye(tls_session, GNUTLS_SHUT_WR); + gnutls_deinit(tls_session); + tls_session = NULL; + gnutls_global_deinit(); + #endif + + tls_active = 0; + continue; + } + + /* Remember that we sent STARTTLS */ + + sent_starttls = (strcmp(outbuffer, "starttls") == 0 || + strcmp(outbuffer, "STARTTLS") == 0); + + /* Fudge: if the command is "starttls_wait", we send the starttls bit, + but we haven't set the flag, so that there is no negotiation. This is for + testing the server's timeout. */ + + if (strcmp(outbuffer, "starttls_wait") == 0) + { + outbuffer[8] = 0; + n = 8; + } + #endif + + printf(">>> %s\n", outbuffer); + strcpy(outbuffer + n, "\r\n"); + + /* Turn "\n" and "\r" into the relevant characters. This is a hack. */ + + while ((escape = strstr(outbuffer, "\\r")) != NULL) + { + *escape = '\r'; + memmove(escape + 1, escape + 2, (n + 2) - (escape - outbuffer) - 2); + n--; + } + + while ((escape = strstr(outbuffer, "\\n")) != NULL) + { + *escape = '\n'; + memmove(escape + 1, escape + 2, (n + 2) - (escape - outbuffer) - 2); + n--; + } + + /* OK, do it */ + + alarm(timeout); + if (tls_active) + { + #ifdef HAVE_OPENSSL + rc = SSL_write (ssl, outbuffer, n + 2); + #endif + #ifdef HAVE_GNUTLS + rc = gnutls_record_send(tls_session, CS outbuffer, n + 2); + if (rc < 0) + { + printf("GnuTLS write error: %s\n", gnutls_strerror(rc)); + exit(1); + } + #endif + } + else + { + rc = write(sock, outbuffer, n + 2); + } + alarm(0); + + if (rc < 0) + { + printf("Write error: %s\n", strerror(errno)); + exit(1); + } + } + } + +printf("End of script\n"); +close(sock); + +exit(0); +} + +/* End of client.c */ |