Merge branch 'dbmjz'
[exim.git] / test / src / client.c
CommitLineData
c55a77db
PH
1/* A little hacked up program that makes a TCP/IP call and reads a script to
2drive it, for testing Exim server code running as a daemon. It's got a bit
3messy with the addition of support for either OpenSSL or GnuTLS. The code for
4those was hacked out of Exim itself. */
5
6/* ANSI C standard includes */
7
8#include <ctype.h>
9#include <signal.h>
10#include <stdarg.h>
11#include <stddef.h>
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15#include <time.h>
16
17/* Unix includes */
18
19#include <errno.h>
20#include <dirent.h>
21#include <sys/types.h>
22
23#include <netinet/in_systm.h>
24#include <netinet/in.h>
25#include <netinet/ip.h>
26
27#include <netdb.h>
28#include <arpa/inet.h>
29#include <sys/time.h>
30#include <sys/resource.h>
31#include <sys/socket.h>
32#include <sys/stat.h>
33#include <fcntl.h>
34#include <unistd.h>
35#include <utime.h>
36
37#ifdef AF_INET6
38#define HAVE_IPV6 1
39#endif
40
41#ifndef S_ADDR_TYPE
42#define S_ADDR_TYPE u_long
43#endif
44
45typedef unsigned char uschar;
46
47#define CS (char *)
48#define US (unsigned char *)
49
50#define FALSE 0
51#define TRUE 1
52
53
54
55static int sigalrm_seen = 0;
56
57
58/* TLS support can be optionally included, either for OpenSSL or GnuTLS. The
59latter needs a whole pile of tables. */
60
61#ifdef HAVE_OPENSSL
62#define HAVE_TLS
63#include <openssl/crypto.h>
64#include <openssl/x509.h>
65#include <openssl/pem.h>
66#include <openssl/ssl.h>
67#include <openssl/err.h>
68#include <openssl/rand.h>
69#endif
70
71
72#ifdef HAVE_GNUTLS
73#define HAVE_TLS
74#include <gnutls/gnutls.h>
75#include <gnutls/x509.h>
76
77#define DH_BITS 768
c55a77db
PH
78
79/* Local static variables for GNUTLS */
80
c55a77db
PH
81static gnutls_dh_params dh_params = NULL;
82
83static gnutls_certificate_credentials_t x509_cred = NULL;
84static gnutls_session tls_session = NULL;
85
86static int ssl_session_timeout = 200;
87
88/* Priorities for TLS algorithms to use. */
89
90static const int protocol_priority[16] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 };
91
92static const int kx_priority[16] = {
93 GNUTLS_KX_RSA,
94 GNUTLS_KX_DHE_DSS,
95 GNUTLS_KX_DHE_RSA,
c55a77db
PH
96 0 };
97
98static int default_cipher_priority[16] = {
99 GNUTLS_CIPHER_AES_256_CBC,
100 GNUTLS_CIPHER_AES_128_CBC,
101 GNUTLS_CIPHER_3DES_CBC,
102 GNUTLS_CIPHER_ARCFOUR_128,
103 0 };
104
105static const int mac_priority[16] = {
106 GNUTLS_MAC_SHA,
107 GNUTLS_MAC_MD5,
108 0 };
109
110static const int comp_priority[16] = { GNUTLS_COMP_NULL, 0 };
111static const int cert_type_priority[16] = { GNUTLS_CRT_X509, 0 };
112
113#endif
114
115
116
117
118/*************************************************
119* SIGALRM handler - crash out *
120*************************************************/
121
122static void
123sigalrm_handler_crash(int sig)
124{
125sig = sig; /* Keep picky compilers happy */
126printf("\nClient timed out\n");
127exit(99);
128}
129
130
131/*************************************************
132* SIGALRM handler - set flag *
133*************************************************/
134
135static void
136sigalrm_handler_flag(int sig)
137{
138sig = sig; /* Keep picky compilers happy */
139sigalrm_seen = 1;
140}
141
142
143
144/****************************************************************************/
145/****************************************************************************/
146
147#ifdef HAVE_OPENSSL
148/*************************************************
149* Start an OpenSSL TLS session *
150*************************************************/
151
152int tls_start(int sock, SSL **ssl, SSL_CTX *ctx)
153{
154int rc;
155static const char *sid_ctx = "exim";
156
157RAND_load_file("client.c", -1); /* Not *very* random! */
158
159*ssl = SSL_new (ctx);
160SSL_set_session_id_context(*ssl, sid_ctx, strlen(sid_ctx));
161SSL_set_fd (*ssl, sock);
162SSL_set_connect_state(*ssl);
163
164signal(SIGALRM, sigalrm_handler_flag);
165sigalrm_seen = 0;
166alarm(5);
167rc = SSL_connect (*ssl);
168alarm(0);
169
170if (sigalrm_seen)
171 {
172 printf("SSL_connect timed out\n");
173 return 0;
174 }
175
176if (rc <= 0)
177 {
178 ERR_print_errors_fp(stdout);
179 return 0;
180 }
181
182printf("SSL connection using %s\n", SSL_get_cipher (*ssl));
183return 1;
184}
185
186
187/*************************************************
188* SSL Information callback *
189*************************************************/
190
191static void
192info_callback(SSL *s, int where, int ret)
193{
194where = where;
195ret = ret;
196printf("SSL info: %s\n", SSL_state_string_long(s));
197}
198#endif
199
200
201/****************************************************************************/
202/****************************************************************************/
203
204
205#ifdef HAVE_GNUTLS
206/*************************************************
207* Handle GnuTLS error *
208*************************************************/
209
210/* Called from lots of places when errors occur before actually starting to do
211the TLS handshake, that is, while the session is still in clear.
212
213Argument:
214 prefix prefix text
215 err a GnuTLS error number, or 0 if local error
216
217Returns: doesn't - it dies
218*/
219
220static void
221gnutls_error(uschar *prefix, int err)
222{
bb727765 223fprintf(stderr, "GnuTLS connection error: %s:", prefix);
c55a77db
PH
224if (err != 0) fprintf(stderr, " %s", gnutls_strerror(err));
225fprintf(stderr, "\n");
226exit(1);
227}
228
229
230
231/*************************************************
bb727765 232* Setup up DH parameters *
c55a77db
PH
233*************************************************/
234
235/* For the test suite, the parameters should always be available in the spool
236directory. */
237
238static void
bb727765 239init_dh(void)
c55a77db
PH
240{
241int fd;
242int ret;
243gnutls_datum m;
244uschar filename[200];
245struct stat statbuf;
246
247/* Initialize the data structures for holding the parameters */
248
c55a77db
PH
249ret = gnutls_dh_params_init(&dh_params);
250if (ret < 0) gnutls_error(US"init dh_params", ret);
251
252/* Open the cache file for reading and if successful, read it and set up the
bb727765 253parameters. */
c55a77db
PH
254
255fd = open("aux-fixed/gnutls-params", O_RDONLY, 0);
256if (fd < 0)
257 {
258 fprintf(stderr, "Failed to open spool/gnutls-params: %s\n", strerror(errno));
259 exit(1);
260 }
261
262if (fstat(fd, &statbuf) < 0)
263 {
264 (void)close(fd);
265 return gnutls_error(US"TLS cache stat failed", 0);
266 }
267
268m.size = statbuf.st_size;
269m.data = malloc(m.size);
270if (m.data == NULL)
271 return gnutls_error(US"memory allocation failed", 0);
272if (read(fd, m.data, m.size) != m.size)
273 return gnutls_error(US"TLS cache read failed", 0);
274(void)close(fd);
275
c55a77db
PH
276ret = gnutls_dh_params_import_pkcs3(dh_params, &m, GNUTLS_X509_FMT_PEM);
277if (ret < 0) return gnutls_error(US"DH params import", ret);
278free(m.data);
279}
280
281
282
283
284/*************************************************
285* Initialize for GnuTLS *
286*************************************************/
287
288/*
289Arguments:
290 certificate certificate file
291 privatekey private key file
292*/
293
294static void
295tls_init(uschar *certificate, uschar *privatekey)
296{
297int rc;
298
299rc = gnutls_global_init();
300if (rc < 0) gnutls_error(US"gnutls_global_init", rc);
301
bb727765 302/* Read D-H parameters from the cache file. */
c55a77db 303
bb727765 304init_dh();
c55a77db
PH
305
306/* Create the credentials structure */
307
308rc = gnutls_certificate_allocate_credentials(&x509_cred);
309if (rc < 0) gnutls_error(US"certificate_allocate_credentials", rc);
310
311/* Set the certificate and private keys */
312
313if (certificate != NULL)
314 {
315 rc = gnutls_certificate_set_x509_key_file(x509_cred, CS certificate,
316 CS privatekey, GNUTLS_X509_FMT_PEM);
317 if (rc < 0) gnutls_error("gnutls_certificate", rc);
318 }
319
320/* Associate the parameters with the x509 credentials structure. */
321
322gnutls_certificate_set_dh_params(x509_cred, dh_params);
c55a77db
PH
323}
324
325
326
327/*************************************************
328* Initialize a single GNUTLS session *
329*************************************************/
330
331static gnutls_session
332tls_session_init(void)
333{
334gnutls_session session;
335
336gnutls_init(&session, GNUTLS_CLIENT);
337
338gnutls_cipher_set_priority(session, default_cipher_priority);
339gnutls_compression_set_priority(session, comp_priority);
340gnutls_kx_set_priority(session, kx_priority);
341gnutls_protocol_set_priority(session, protocol_priority);
342gnutls_mac_set_priority(session, mac_priority);
343
344gnutls_cred_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred);
345
346gnutls_dh_set_prime_bits(session, DH_BITS);
347gnutls_db_set_cache_expiration(session, ssl_session_timeout);
348
349return session;
350}
351#endif
352
353
354/****************************************************************************/
355/****************************************************************************/
356
357
358
359
360/*************************************************
361* Main Program *
362*************************************************/
363
364/* Usage: client
365 <IP address>
366 <port>
367 [<outgoing interface>]
368 [<cert file>]
369 [<key file>]
370*/
371
372int main(int argc, char **argv)
373{
374struct sockaddr *s_ptr;
375struct sockaddr_in s_in4;
376char *interface = NULL;
377char *address = NULL;
378char *certfile = NULL;
379char *keyfile = NULL;
380int argi = 1;
381int host_af, port, s_len, rc, sock, save_errno;
382int timeout = 1;
383int tls_active = 0;
384int sent_starttls = 0;
385int tls_on_connect = 0;
386
387#if HAVE_IPV6
388struct sockaddr_in6 s_in6;
389#endif
390
391#ifdef HAVE_OPENSSL
392SSL_CTX* ctx;
393SSL* ssl;
394#endif
395
396unsigned char outbuffer[10240];
397unsigned char inbuffer[10240];
398unsigned char *inptr = inbuffer;
399
400*inptr = 0; /* Buffer empty */
401
402/* Options */
403
404while (argc >= argi + 1 && argv[argi][0] == '-')
405 {
406 if (strcmp(argv[argi], "-tls-on-connect") == 0)
407 {
408 tls_on_connect = 1;
409 argi++;
410 }
411 else if (argv[argi][1] == 't' && isdigit(argv[argi][2]))
412 {
413 timeout = atoi(argv[argi]+1);
414 argi++;
415 }
416 else
417 {
418 printf("Unrecognized option %s\n", argv[argi]);
419 exit(1);
420 }
421 }
422
423/* Mandatory 1st arg is IP address */
424
425if (argc < argi+1)
426 {
427 printf("No IP address given\n");
428 exit(1);
429 }
430
431address = argv[argi++];
432host_af = (strchr(address, ':') != NULL)? AF_INET6 : AF_INET;
433
434/* Mandatory 2nd arg is port */
435
436if (argc < argi+1)
437 {
438 printf("No port number given\n");
439 exit(1);
440 }
441
442port = atoi(argv[argi++]);
443
444/* Optional next arg is interface */
445
446if (argc > argi &&
447 (isdigit((unsigned char)argv[argi][0]) || argv[argi][0] == ':'))
448 interface = argv[argi++];
449
450/* Any more arguments are the name of a certificate file and key file */
451
452if (argc > argi) certfile = argv[argi++];
453if (argc > argi) keyfile = argv[argi++];
454
455
456#if HAVE_IPV6
457/* For an IPv6 address, use an IPv6 sockaddr structure. */
458
459if (host_af == AF_INET6)
460 {
461 s_ptr = (struct sockaddr *)&s_in6;
462 s_len = sizeof(s_in6);
463 }
464else
465#endif
466
467/* For an IPv4 address, use an IPv4 sockaddr structure,
468even on an IPv6 system. */
469
470 {
471 s_ptr = (struct sockaddr *)&s_in4;
472 s_len = sizeof(s_in4);
473 }
474
475printf("Connecting to %s port %d ... ", address, port);
476
477sock = socket(host_af, SOCK_STREAM, 0);
478if (sock < 0)
479 {
480 printf("socket creation failed: %s\n", strerror(errno));
481 exit(1);
482 }
483
484/* Bind to a specific interface if requested. On an IPv6 system, this has
485to be of the same family as the address we are calling. On an IPv4 system the
486test is redundant, but it keeps the code tidier. */
487
488if (interface != NULL)
489 {
490 int interface_af = (strchr(interface, ':') != NULL)? AF_INET6 : AF_INET;
491
492 if (interface_af == host_af)
493 {
494 #if HAVE_IPV6
495
496 /* Set up for IPv6 binding */
497
498 if (host_af == AF_INET6)
499 {
500 memset(&s_in6, 0, sizeof(s_in6));
501 s_in6.sin6_family = AF_INET6;
502 s_in6.sin6_port = 0;
503 if (inet_pton(AF_INET6, interface, &s_in6.sin6_addr) != 1)
504 {
505 printf("Unable to parse \"%s\"", interface);
506 exit(1);
507 }
508 }
509 else
510 #endif
511
512 /* Set up for IPv4 binding */
513
514 {
515 memset(&s_in4, 0, sizeof(s_in4));
516 s_in4.sin_family = AF_INET;
517 s_in4.sin_port = 0;
518 s_in4.sin_addr.s_addr = (S_ADDR_TYPE)inet_addr(interface);
519 }
520
521 /* Bind */
522
523 if (bind(sock, s_ptr, s_len) < 0)
524 {
525 printf("Unable to bind outgoing SMTP call to %s: %s",
526 interface, strerror(errno));
527 exit(1);
528 }
529 }
530 }
531
532/* Set up a remote IPv6 address */
533
534#if HAVE_IPV6
535if (host_af == AF_INET6)
536 {
537 memset(&s_in6, 0, sizeof(s_in6));
538 s_in6.sin6_family = AF_INET6;
539 s_in6.sin6_port = htons(port);
540 if (inet_pton(host_af, address, &s_in6.sin6_addr) != 1)
541 {
542 printf("Unable to parse \"%s\"", address);
543 exit(1);
544 }
545 }
546else
547#endif
548
549/* Set up a remote IPv4 address */
550
551 {
552 memset(&s_in4, 0, sizeof(s_in4));
553 s_in4.sin_family = AF_INET;
554 s_in4.sin_port = htons(port);
555 s_in4.sin_addr.s_addr = (S_ADDR_TYPE)inet_addr(address);
556 }
557
558/* SIGALRM handler crashes out */
559
560signal(SIGALRM, sigalrm_handler_crash);
561alarm(timeout);
562rc = connect(sock, s_ptr, s_len);
563save_errno = errno;
564alarm(0);
565
566/* A failure whose error code is "Interrupted system call" is in fact
567an externally applied timeout if the signal handler has been run. */
568
569if (rc < 0)
570 {
571 close(sock);
572 printf("failed: %s\n", strerror(save_errno));
573 exit(1);
574 }
575
576printf("connected\n");
577
578
579/* --------------- Set up for OpenSSL --------------- */
580
581#ifdef HAVE_OPENSSL
582SSL_library_init();
583SSL_load_error_strings();
584
585ctx = SSL_CTX_new(SSLv23_method());
586if (ctx == NULL)
587 {
588 printf ("SSL_CTX_new failed\n");
589 exit(1);
590 }
591
592if (certfile != NULL)
593 {
594 if (!SSL_CTX_use_certificate_file(ctx, certfile, SSL_FILETYPE_PEM))
595 {
596 printf("SSL_CTX_use_certificate_file failed\n");
597 exit(1);
598 }
599 printf("Certificate file = %s\n", certfile);
600 }
601
602if (keyfile != NULL)
603 {
604 if (!SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM))
605 {
606 printf("SSL_CTX_use_PrivateKey_file failed\n");
607 exit(1);
608 }
609 printf("Key file = %s\n", keyfile);
610 }
611
612SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_BOTH);
613SSL_CTX_set_timeout(ctx, 200);
614SSL_CTX_set_info_callback(ctx, (void (*)())info_callback);
615#endif
616
617
618/* --------------- Set up for GnuTLS --------------- */
619
620#ifdef HAVE_GNUTLS
621if (certfile != NULL) printf("Certificate file = %s\n", certfile);
622if (keyfile != NULL) printf("Key file = %s\n", keyfile);
623tls_init(certfile, keyfile);
624tls_session = tls_session_init();
625gnutls_transport_set_ptr(tls_session, (gnutls_transport_ptr)sock);
626
627/* When the server asks for a certificate and the client does not have one,
628there is a SIGPIPE error in the gnutls_handshake() function for some reason
629that is not understood. As luck would have it, this has never hit Exim itself
630because it ignores SIGPIPE errors. Doing the same here allows it all to work as
631one wants. */
632
633signal(SIGPIPE, SIG_IGN);
634#endif
635
636/* ---------------------------------------------- */
637
638
639/* Start TLS session if configured to do so without STARTTLS */
640
641#ifdef HAVE_TLS
642if (tls_on_connect)
643 {
644 printf("Attempting to start TLS\n");
645
646 #ifdef HAVE_OPENSSL
647 tls_active = tls_start(sock, &ssl, ctx);
648 #endif
649
650 #ifdef HAVE_GNUTLS
651 sigalrm_seen = FALSE;
652 alarm(timeout);
653 tls_active = gnutls_handshake(tls_session) >= 0;
654 alarm(0);
655 #endif
656
657 if (!tls_active)
658 printf("Failed to start TLS\n");
659 else
660 printf("Succeeded in starting TLS\n");
661 }
662#endif
663
664while (fgets(outbuffer, sizeof(outbuffer), stdin) != NULL)
665 {
666 int n = (int)strlen(outbuffer);
667 while (n > 0 && isspace(outbuffer[n-1])) n--;
668 outbuffer[n] = 0;
669
670 /* Expect incoming */
671
672 if (strncmp(outbuffer, "??? ", 4) == 0)
673 {
674 unsigned char *lineptr;
675 printf("%s\n", outbuffer);
676
677 if (*inptr == 0) /* Refill input buffer */
678 {
679 if (tls_active)
680 {
681 #ifdef HAVE_OPENSSL
682 rc = SSL_read (ssl, inbuffer, sizeof(inbuffer) - 1);
683 #endif
684 #ifdef HAVE_GNUTLS
685 rc = gnutls_record_recv(tls_session, CS inbuffer, sizeof(inbuffer) - 1);
686 #endif
687 }
688 else
689 {
690 alarm(timeout);
691 rc = read(sock, inbuffer, sizeof(inbuffer));
692 alarm(0);
693 }
694
695 if (rc < 0)
696 {
697 printf("Read error %s\n", strerror(errno));
698 exit(1) ;
699 }
700 else if (rc == 0)
701 {
702 printf("Unexpected EOF read\n");
703 close(sock);
704 exit(1);
705 }
706 else
707 {
708 inbuffer[rc] = 0;
709 inptr = inbuffer;
710 }
711 }
712
713 lineptr = inptr;
714 while (*inptr != 0 && *inptr != '\r' && *inptr != '\n') inptr++;
715 if (*inptr != 0)
716 {
717 *inptr++ = 0;
718 if (*inptr == '\n') inptr++;
719 }
720
721 printf("<<< %s\n", lineptr);
722 if (strncmp(lineptr, outbuffer + 4, (int)strlen(outbuffer) - 4) != 0)
723 {
724 printf("\n******** Input mismatch ********\n");
725 exit(1);
726 }
727
728 #ifdef HAVE_TLS
729 if (sent_starttls)
730 {
731 if (lineptr[0] == '2')
732 {
733 printf("Attempting to start TLS\n");
734 fflush(stdout);
735
736 #ifdef HAVE_OPENSSL
737 tls_active = tls_start(sock, &ssl, ctx);
738 #endif
739
740 #ifdef HAVE_GNUTLS
741 sigalrm_seen = FALSE;
742 alarm(timeout);
743 tls_active = gnutls_handshake(tls_session) >= 0;
744 alarm(0);
745 #endif
746
747 if (!tls_active)
748 {
749 printf("Failed to start TLS\n");
750 fflush(stdout);
751 }
752 else
753 printf("Succeeded in starting TLS\n");
754 }
755 else printf("Abandoning TLS start attempt\n");
756 }
757 sent_starttls = 0;
758 #endif
759 }
760
761 /* Wait for a bit before proceeding */
762
763 else if (strncmp(outbuffer, "+++ ", 4) == 0)
764 {
765 printf("%s\n", outbuffer);
766 sleep(atoi(outbuffer + 4));
767 }
768
769 /* Send outgoing, but barf if unconsumed incoming */
770
771 else
772 {
773 unsigned char *escape;
774
775 if (*inptr != 0)
776 {
777 printf("Unconsumed input: %s", inptr);
778 printf(" About to send: %s\n", outbuffer);
779 exit(1);
780 }
781
782 #ifdef HAVE_TLS
783
784 /* Shutdown TLS */
785
786 if (strcmp(outbuffer, "stoptls") == 0 ||
787 strcmp(outbuffer, "STOPTLS") == 0)
788 {
789 if (!tls_active)
790 {
791 printf("STOPTLS read when TLS not active\n");
792 exit(1);
793 }
794 printf("Shutting down TLS encryption\n");
795
796 #ifdef HAVE_OPENSSL
797 SSL_shutdown(ssl);
798 SSL_free(ssl);
799 #endif
800
801 #ifdef HAVE_GNUTLS
802 gnutls_bye(tls_session, GNUTLS_SHUT_WR);
803 gnutls_deinit(tls_session);
804 tls_session = NULL;
805 gnutls_global_deinit();
806 #endif
807
808 tls_active = 0;
809 continue;
810 }
811
812 /* Remember that we sent STARTTLS */
813
814 sent_starttls = (strcmp(outbuffer, "starttls") == 0 ||
815 strcmp(outbuffer, "STARTTLS") == 0);
816
817 /* Fudge: if the command is "starttls_wait", we send the starttls bit,
818 but we haven't set the flag, so that there is no negotiation. This is for
819 testing the server's timeout. */
820
821 if (strcmp(outbuffer, "starttls_wait") == 0)
822 {
823 outbuffer[8] = 0;
824 n = 8;
825 }
826 #endif
827
828 printf(">>> %s\n", outbuffer);
829 strcpy(outbuffer + n, "\r\n");
830
831 /* Turn "\n" and "\r" into the relevant characters. This is a hack. */
832
833 while ((escape = strstr(outbuffer, "\\r")) != NULL)
834 {
835 *escape = '\r';
836 memmove(escape + 1, escape + 2, (n + 2) - (escape - outbuffer) - 2);
837 n--;
838 }
839
840 while ((escape = strstr(outbuffer, "\\n")) != NULL)
841 {
842 *escape = '\n';
843 memmove(escape + 1, escape + 2, (n + 2) - (escape - outbuffer) - 2);
844 n--;
845 }
846
847 /* OK, do it */
848
849 alarm(timeout);
850 if (tls_active)
851 {
852 #ifdef HAVE_OPENSSL
853 rc = SSL_write (ssl, outbuffer, n + 2);
854 #endif
855 #ifdef HAVE_GNUTLS
856 rc = gnutls_record_send(tls_session, CS outbuffer, n + 2);
857 if (rc < 0)
858 {
859 printf("GnuTLS write error: %s\n", gnutls_strerror(rc));
860 exit(1);
861 }
862 #endif
863 }
864 else
865 {
866 rc = write(sock, outbuffer, n + 2);
867 }
868 alarm(0);
869
870 if (rc < 0)
871 {
872 printf("Write error: %s\n", strerror(errno));
873 exit(1);
874 }
875 }
876 }
877
878printf("End of script\n");
879close(sock);
880
881exit(0);
882}
883
884/* End of client.c */