+}
+
+
+
+
+/*************************************************
+* Main Program *
+*************************************************/
+
+const char * const HELP_MESSAGE = "\n\
+Usage: client\n"
+#ifdef HAVE_TLS
+"\
+ [-tls-on-connect]\n\
+ [-ocsp]\n"
+# ifdef HAVE_GNUTLS
+"\
+ [-p priority-string]\n"
+# endif
+#endif
+"\
+ [-tn] n seconds timeout\n\
+ <IP address>\n\
+ <port>\n\
+ [<outgoing interface>]\n\
+ [<cert file>]\n\
+ [<key file>]\n\
+\n";
+
+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;
+char *end = NULL;
+int argi = 1;
+int host_af, port, s_len, rc, save_errno;
+int timeout = 5;
+int tls_on_connect = 0;
+long tmplong;
+
+#if HAVE_IPV6
+struct sockaddr_in6 s_in6;
+#endif
+
+srv_ctx srv;
+
+unsigned char inbuffer[100 * 1024];
+unsigned char *inptr = inbuffer;
+
+*inptr = 0; /* Buffer empty */
+srv.tls_active = 0;
+srv.sent_starttls = 0;
+
+/* Options */
+
+while (argc >= argi + 1 && argv[argi][0] == '-')
+ {
+ if (strcmp(argv[argi], "-help") == 0 ||
+ strcmp(argv[argi], "--help") == 0 ||
+ strcmp(argv[argi], "-h") == 0)
+ {
+ puts(HELP_MESSAGE);
+ exit(0);
+ }
+ if (strcmp(argv[argi], "-tls-on-connect") == 0)
+ {
+ tls_on_connect = 1;
+ argi++;
+ }
+#ifdef HAVE_TLS
+ else if (strcmp(argv[argi], "-ocsp") == 0)
+ {
+ if (argc < ++argi + 1)
+ {
+ fprintf(stderr, "Missing required certificate file for ocsp option\n");
+ exit(96);
+ }
+ ocsp_stapling = argv[argi++];
+ }
+# ifdef HAVE_GNUTLS
+ else if (strcmp(argv[argi], "-p") == 0)
+ {
+ if (argc < ++argi + 1)
+ {
+ fprintf(stderr, "Missing priority string\n");
+ exit(96);
+ }
+ pri_string = argv[argi++];
+ }
+#endif
+
+#endif
+ else if (argv[argi][1] == 't' && isdigit(argv[argi][2]))
+ {
+ tmplong = strtol(argv[argi]+2, &end, 10);
+ if (end == argv[argi]+2 || *end)
+ {
+ fprintf(stderr, "Failed to parse seconds from option <%s>\n",
+ argv[argi]);
+ exit(95);
+ }
+ if (tmplong > 10000L)
+ {
+ fprintf(stderr, "Unreasonably long wait of %ld seconds requested\n",
+ tmplong);
+ exit(94);
+ }
+ if (tmplong < 0L)
+ {
+ fprintf(stderr, "Timeout must not be negative (%ld)\n", tmplong);
+ exit(93);
+ }
+ timeout = (int) tmplong;
+ argi++;
+ }
+ else
+ {
+ fprintf(stderr, "Unrecognized option %s\n", argv[argi]);
+ exit(92);
+ }
+ }
+
+/* Mandatory 1st arg is IP address */
+
+if (argc < argi+1)
+ {
+ fprintf(stderr, "No IP address given\n");
+ exit(91);
+ }
+
+address = argv[argi++];
+host_af = (strchr(address, ':') != NULL)? AF_INET6 : AF_INET;
+
+/* Mandatory 2nd arg is port */
+
+if (argc < argi+1)
+ {
+ fprintf(stderr, "No port number given\n");
+ exit(90);
+ }
+
+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);
+
+srv.sock = socket(host_af, SOCK_STREAM, 0);
+if (srv.sock < 0)
+ {
+ printf("socket creation failed: %s\n", strerror(errno));
+ exit(89);
+ }
+
+/* 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(88);
+ }
+ }
+ 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(srv.sock, s_ptr, s_len) < 0)
+ {
+ printf("Unable to bind outgoing SMTP call to %s: %s",
+ interface, strerror(errno));
+ exit(87);
+ }
+ }
+ }
+
+/* Set up a remote IPv6 address */
+
+#if HAVE_IPV6
+if (host_af == AF_INET6)
+ {
+# ifdef HAVE_GETADDRINFO
+ struct addrinfo hints, *res;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET6;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_NUMERICHOST;
+ if ((rc = getaddrinfo(address, NULL, &hints, &res)) != 0 || res == NULL)
+ {
+ printf("unable to parse \"%s\" as an IP address: %s\n", address,
+ rc == 0 ? "NULL result returned" : gai_strerror(rc));
+ exit(86);
+ }
+ memcpy(&s_in6, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+# else
+ memset(&s_in6, 0, sizeof(s_in6));
+ s_in6.sin6_family = AF_INET6;
+ if (inet_pton(host_af, address, &s_in6.sin6_addr) != 1)
+ {
+ printf("Unable to parse \"%s\"", address);
+ exit(86);
+ }
+# endif
+ s_in6.sin6_port = htons(port);
+ }
+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(srv.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(srv.sock);
+ printf("connect failed: %s\n", strerror(save_errno));
+ exit(85);
+ }
+
+#ifdef TCP_QUICKACK
+ {
+ int off = 0;
+ (void) setsockopt(srv.sock, IPPROTO_TCP, TCP_QUICKACK, US &off, sizeof(off));
+ }
+#endif
+
+printf("connected\n");
+
+
+/* --------------- Set up for OpenSSL --------------- */
+
+#ifdef HAVE_OPENSSL
+SSL_library_init();
+SSL_load_error_strings();
+
+if (!(srv.ctx = SSL_CTX_new(SSLv23_method())))
+ {
+ printf ("SSL_CTX_new failed\n");
+ exit(84);
+ }
+
+if (certfile)
+ {
+ if (!SSL_CTX_use_certificate_file(srv.ctx, certfile, SSL_FILETYPE_PEM))
+ {
+ printf("SSL_CTX_use_certificate_file failed\n");
+ exit(83);
+ }
+ printf("Certificate file = %s\n", certfile);
+ }
+
+if (keyfile)
+ {
+ if (!SSL_CTX_use_PrivateKey_file(srv.ctx, keyfile, SSL_FILETYPE_PEM))
+ {
+ printf("SSL_CTX_use_PrivateKey_file failed\n");
+ exit(82);
+ }
+ printf("Key file = %s\n", keyfile);
+ }
+
+SSL_CTX_set_session_cache_mode(srv.ctx, SSL_SESS_CACHE_BOTH);
+SSL_CTX_set_timeout(srv.ctx, 200);
+SSL_CTX_set_info_callback(srv.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(US certfile, US keyfile);
+tls_session = tls_session_init();
+#ifdef HAVE_OCSP
+if (ocsp_stapling)
+ gnutls_ocsp_status_request_enable_client(tls_session, NULL, 0, NULL);
+#endif
+gnutls_transport_set_ptr(tls_session, (gnutls_transport_ptr_t)(intptr_t)srv.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
+ srv.tls_active = tls_start(srv.sock, &srv.ssl, srv.ctx);
+#endif
+
+#ifdef HAVE_GNUTLS
+ {
+ int rc;
+ sigalrm_seen = FALSE;
+ alarm(timeout);
+ do {
+ rc = gnutls_handshake(tls_session);
+ } while (rc < 0 && gnutls_error_is_fatal(rc) == 0);
+ srv.tls_active = rc >= 0;
+ alarm(0);
+
+ if (!srv.tls_active) printf("%s\n", gnutls_strerror(rc));
+ }
+#endif
+
+ if (!srv.tls_active)
+ printf("Failed to start TLS\n");
+#if defined(HAVE_GNUTLS) && defined(HAVE_OCSP)
+ else if ( ocsp_stapling
+ && gnutls_ocsp_status_request_is_checked(tls_session, 0) == 0)
+ printf("Failed to verify certificate status\n");
+#endif
+ else
+ printf("Succeeded in starting TLS\n");
+ }
+#endif
+
+do_file(&srv, stdin, timeout, inbuffer, sizeof(inbuffer), inptr);