X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/5ddc9771fa4d7861d3e5bfd6ea30c054883eaf40..31d87352f9508ba8e570d2462667907d8979b93d:/test/src/client.c?ds=sidebyside diff --git a/test/src/client.c b/test/src/client.c index 2b73098f5..2544ec84a 100644 --- a/test/src/client.c +++ b/test/src/client.c @@ -36,6 +36,9 @@ ripped from the openssl ocsp and s_client utilities. */ #include #include +/* Set to TRUE to enable debug output */ +#define DEBUG if (FALSE) + #ifdef AF_INET6 #define HAVE_IPV6 1 #endif @@ -83,7 +86,7 @@ latter needs a whole pile of tables. */ # include # include # if GNUTLS_VERSION_NUMBER >= 0x030103 -# define HAVE_OCSP +# define HAVE_GNUTLS_OCSP # include # endif # ifndef GNUTLS_NO_EXTENSIONS @@ -103,7 +106,7 @@ static int ssl_session_timeout = 200; /* Priorities for TLS algorithms to use. */ -#if GNUTLS_VERSION_NUMBER < 0x030400 +# if GNUTLS_VERSION_NUMBER < 0x030400 static const int protocol_priority[16] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 }; static const int kx_priority[16] = { @@ -125,7 +128,7 @@ static const int mac_priority[16] = { 0 }; static const int comp_priority[16] = { GNUTLS_COMP_NULL, 0 }; -#endif +# endif #endif /*HAVE_GNUTLS*/ @@ -133,6 +136,8 @@ static const int comp_priority[16] = { GNUTLS_COMP_NULL, 0 }; #ifdef HAVE_TLS char * ocsp_stapling = NULL; +char * pri_string = NULL; +int tls_quiet = 0; #endif @@ -166,59 +171,25 @@ sigalrm_seen = 1; /****************************************************************************/ #ifdef HAVE_OPENSSL +# ifndef DISABLE_OCSP -X509_STORE * -setup_verify(BIO *bp, char *CAfile, char *CApath) -{ - X509_STORE *store; - X509_LOOKUP *lookup; - if(!(store = X509_STORE_new())) goto end; - lookup=X509_STORE_add_lookup(store,X509_LOOKUP_file()); - if (lookup == NULL) goto end; - if (CAfile) { - if(!X509_LOOKUP_load_file(lookup,CAfile,X509_FILETYPE_PEM)) { - BIO_printf(bp, "Error loading file %s\n", CAfile); - goto end; - } - } else X509_LOOKUP_load_file(lookup,NULL,X509_FILETYPE_DEFAULT); - - lookup=X509_STORE_add_lookup(store,X509_LOOKUP_hash_dir()); - if (lookup == NULL) goto end; - if (CApath) { - if(!X509_LOOKUP_add_dir(lookup,CApath,X509_FILETYPE_PEM)) { - BIO_printf(bp, "Error loading directory %s\n", CApath); - goto end; - } - } else X509_LOOKUP_add_dir(lookup,NULL,X509_FILETYPE_DEFAULT); - - ERR_clear_error(); - return store; - end: - X509_STORE_free(store); - return NULL; -} - - -#ifndef DISABLE_OCSP static STACK_OF(X509) * -cert_stack_from_store(X509_STORE * store) +chain_from_pem_file(const uschar * file) { -STACK_OF(X509_OBJECT) * roots= store->objs; -STACK_OF(X509) * sk = sk_X509_new_null(); -int i; +BIO * bp; +X509 * x; +STACK_OF(X509) * sk; -for(i = sk_X509_OBJECT_num(roots) - 1; i >= 0; i--) - { - X509_OBJECT * tmp_obj= sk_X509_OBJECT_value(roots, i); - if(tmp_obj->type == X509_LU_X509) - { - X509 * x = tmp_obj->data.x509; - sk_X509_push(sk, x); - } - } +if (!(sk = sk_X509_new_null())) return NULL; +if (!(bp = BIO_new_file(CS file, "r"))) return NULL; +while ((x = PEM_read_bio_X509(bp, NULL, 0, NULL))) + sk_X509_push(sk, x); +BIO_free(bp); return sk; } + + static void cert_stack_free(STACK_OF(X509) * sk) { @@ -234,8 +205,6 @@ const unsigned char *p; int len; OCSP_RESPONSE *rsp; OCSP_BASICRESP *bs; -char *CAfile = NULL; -X509_STORE *store = NULL; STACK_OF(X509) * sk; int ret = 1; @@ -243,31 +212,28 @@ len = SSL_get_tlsext_status_ocsp_resp(s, &p); /*BIO_printf(arg, "OCSP response: ");*/ if (!p) { - BIO_printf(arg, "no response received\n"); + BIO_printf(arg, "no OCSP response received\n"); return 1; } if(!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len))) { - BIO_printf(arg, "response parse error\n"); + BIO_printf(arg, "OCSP response parse error\n"); BIO_dump_indent(arg, (char *)p, len, 4); return 0; } if(!(bs = OCSP_response_get1_basic(rsp))) { - BIO_printf(arg, "error parsing response\n"); + BIO_printf(arg, "error parsing OCSP response\n"); return 0; } -CAfile = ocsp_stapling; -if(!(store = setup_verify(arg, CAfile, NULL))) +if (!(sk = chain_from_pem_file((const uschar *)ocsp_stapling))) { BIO_printf(arg, "error in cert setup\n"); return 0; } -sk = cert_stack_from_store(store); - /* OCSP_basic_verify takes a "store" arg, but does not use it for the chain verification, which is all we do when OCSP_NOVERIFY is set. The content from the wire @@ -275,18 +241,17 @@ when OCSP_NOVERIFY is set. The content from the wire if(OCSP_basic_verify(bs, sk, NULL, OCSP_NOVERIFY) <= 0) { - BIO_printf(arg, "Response Verify Failure\n"); + BIO_printf(arg, "OCSP status response verify failure\n"); ERR_print_errors(arg); ret = 0; } else - BIO_printf(arg, "Response verify OK\n"); + BIO_printf(arg, "OCSP status response: good signature\n"); cert_stack_free(sk); -X509_STORE_free(store); return ret; } -#endif +# endif /*DISABLE_OCSP*/ /************************************************* @@ -333,7 +298,7 @@ if (rc <= 0) return 0; } -printf("SSL connection using %s\n", SSL_get_cipher (*ssl)); +/* printf("SSL connection using %s\n", SSL_get_cipher (*ssl)); */ return 1; } @@ -468,7 +433,7 @@ 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); + if (rc < 0) gnutls_error(US"gnutls_certificate", rc); } /* Associate the parameters with the x509 credentials structure. */ @@ -494,7 +459,7 @@ gnutls_session_t session; gnutls_init(&session, GNUTLS_CLIENT | GNUTLS_NO_EXTENSIONS); -#if GNUTLS_VERSION_NUMBER < 0x030400 +# if GNUTLS_VERSION_NUMBER < 0x030400 gnutls_cipher_set_priority(session, default_cipher_priority); gnutls_compression_set_priority(session, comp_priority); gnutls_kx_set_priority(session, kx_priority); @@ -502,10 +467,19 @@ gnutls_protocol_set_priority(session, protocol_priority); gnutls_mac_set_priority(session, mac_priority); gnutls_cred_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred); -#else -gnutls_set_default_priority(session); +# else +if (pri_string) + { + gnutls_priority_t priority_cache; + const char * errpos; + + gnutls_priority_init(&priority_cache, pri_string, &errpos); + gnutls_priority_set(session, priority_cache); + } +else + gnutls_set_default_priority(session); gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred); -#endif +# endif gnutls_dh_set_prime_bits(session, DH_BITS); gnutls_db_set_cache_expiration(session, ssl_session_timeout); @@ -516,183 +490,663 @@ return session; /****************************************************************************/ -/****************************************************************************/ - +/* Turn "\n" and "\r" into the relevant characters. This is a hack. */ +static int +unescape_buf(unsigned char * buf, int len) +{ +unsigned char * s; +unsigned char c, t; +unsigned shift; +for (s = buf; s < buf+len; s++) if (*s == '\\') + { + switch (s[1]) + { + default: c = s[1]; shift = 1; break; + case 'n': c = '\n'; shift = 1; break; + case 'r': c = '\r'; shift = 1; break; + case 'x': + t = s[2]; + if (t >= 'A' && t <= 'F') t -= 'A'-'9'-1; + else if (t >= 'a' && t <= 'f') t -= 'a'-'9'-1; + t -= '0'; + c = (t<<4) & 0xf0; + t = s[3]; + if (t >= 'A' && t <= 'F') t -= 'A'-'9'-1; + else if (t >= 'a' && t <= 'f') t -= 'a'-'9'-1; + t -= '0'; + c |= t & 0xf; + shift = 3; + break; + } + *s = c; + memmove(s+1, s+shift+1, len-shift); + len -= shift; + } +return len; +} -/************************************************* -* Main Program * -*************************************************/ -const char * const HELP_MESSAGE = "\n\ -Usage: client\n" -#ifdef HAVE_TLS -"\ - [-tls-on-connect]\n\ - [-ocsp]\n" +/****************************************************************************/ +typedef struct { + int sock; + int tls_active; +#ifdef HAVE_OPENSSL + SSL_CTX * ctx; + SSL * ssl; #endif -"\ - [-tn] n seconds timeout\n\ - \n\ - \n\ - []\n\ - []\n\ - []\n\ -\n"; + int sent_starttls; +} srv_ctx; -int main(int argc, char **argv) +static void +do_file(srv_ctx * srv, FILE * f, int timeout, + unsigned char * inbuffer, unsigned bsiz, unsigned char * inptr) { -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, sock, save_errno; -int timeout = 5; -int tls_active = 0; -int sent_starttls = 0; -int tls_on_connect = 0; -long tmplong; +unsigned char outbuffer[1024 * 20]; -#if HAVE_IPV6 -struct sockaddr_in6 s_in6; -#endif +while (fgets(CS outbuffer, sizeof(outbuffer), f) != NULL) + { + int n = (int)strlen(CS outbuffer); + int crlf = 1; + int rc; -#ifdef HAVE_OPENSSL -SSL_CTX* ctx; -SSL* ssl; -#endif + /* Strip trailing newline */ + if (outbuffer[n-1] == '\n') outbuffer[--n] = 0; -unsigned char outbuffer[10240]; -unsigned char inbuffer[10240]; -unsigned char *inptr = inbuffer; + /* Expect incoming */ -*inptr = 0; /* Buffer empty */ + if ( strncmp(CS outbuffer, "???", 3) == 0 + && (outbuffer[3] == ' ' || outbuffer[3] == '*' || outbuffer[3] == '?') + ) + { + unsigned char *lineptr; + unsigned exp_eof = outbuffer[3] == '*'; + unsigned resp_optional = outbuffer[3] == '?'; -/* Options */ + printf("%s\n", outbuffer); + n = unescape_buf(outbuffer, n); -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) +nextinput: + if (*inptr == 0) /* Refill input buffer */ { - fprintf(stderr, "Missing required certificate file for ocsp option\n"); - exit(96); - } - ocsp_stapling = argv[argi++]; - } + unsigned char *inbufferp = inbuffer; + alarm(timeout); + for (;;) + { + if (srv->tls_active) + { +#ifdef HAVE_OPENSSL + int error; + DEBUG { printf("call SSL_read\n"); fflush(stdout); } + rc = SSL_read(srv->ssl, inbufferp, bsiz - (inbufferp - inbuffer) - 1); + DEBUG { printf("SSL_read: %d\n", rc); fflush(stdout); } + if (rc <= 0) + switch (error = SSL_get_error(srv->ssl, rc)) + { + case SSL_ERROR_ZERO_RETURN: + break; + case SSL_ERROR_SYSCALL: + printf("%s\n", ERR_error_string(ERR_get_error(), NULL)); + rc = -1; + break; + case SSL_ERROR_SSL: + printf("%s\nTLS terminated\n", ERR_error_string(ERR_get_error(), NULL)); + SSL_shutdown(srv->ssl); + SSL_free(srv->ssl); + srv->tls_active = FALSE; + { /* OpenSSL leaves it in restartsys mode */ + struct sigaction act = {.sa_handler = sigalrm_handler_flag, .sa_flags = 0}; + sigalrm_seen = 1; + sigaction(SIGALRM, &act, NULL); + } + *inptr = 0; + DEBUG { printf("go round\n"); fflush(stdout); } + goto nextinput; + default: + printf("SSL error code %d\n", error); + } #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); - } +#ifdef HAVE_GNUTLS + retry1: + DEBUG { printf("call gnutls_record_recv\n"); fflush(stdout); } + rc = gnutls_record_recv(tls_session, CS inbufferp, bsiz - (inbufferp - inbuffer) - 1); + if (rc < 0) + { + DEBUG { printf("gnutls_record_recv: %s\n", gnutls_strerror(rc)); fflush(stdout); } + if (rc == GNUTLS_E_INTERRUPTED || rc == GNUTLS_E_AGAIN) + goto retry1; + printf("%s\n", gnutls_strerror(rc)); + srv->tls_active = FALSE; + *inptr = 0; + DEBUG { printf("go round\n"); fflush(stdout); } + goto nextinput; + } + DEBUG { printf("gnutls_record_recv: %d\n", rc); fflush(stdout); } +#endif + } + else + { + DEBUG { printf("call read\n"); fflush(stdout); } + rc = read(srv->sock, inbufferp, bsiz - (inbufferp - inbuffer) - 1); + DEBUG { printf("read: %d\n", rc); fflush(stdout); } + } -address = argv[argi++]; -host_af = (strchr(address, ':') != NULL)? AF_INET6 : AF_INET; + if (rc > 0) inbufferp[rc] = '\0'; + if (rc <= 0 || strchr(inbufferp, '\n')) break; + inbufferp += rc; + if (inbufferp >= inbuffer + bsiz) + { + printf("Input buffer overrun, need more than %d bytes input buffer\n", bsiz); + exit(73); + } + DEBUG { printf("read more\n"); } + } + alarm(0); -/* Mandatory 2nd arg is port */ + if (rc < 0) + { + if (errno == EINTR && sigalrm_seen && resp_optional) + continue; /* next scriptline */ + printf("Read error: %s\n", strerror(errno)); + exit(81); + } + else if (rc == 0) + if (exp_eof) + { + printf("Expected EOF read\n"); + continue; + } + else if (resp_optional) + continue; /* next scriptline */ + else + { + printf("Unexpected EOF read\n"); + close(srv->sock); + exit(80); + } + else if (exp_eof) + { + printf("Expected EOF not read\n"); + close(srv->sock); + exit(74); + } + else + inptr = inbuffer; + } + DEBUG { printf("read: '%s'\n", inptr); fflush(stdout); } -if (argc < argi+1) - { - fprintf(stderr, "No port number given\n"); - exit(90); - } + lineptr = inptr; + while (*inptr != 0 && *inptr != '\r' && *inptr != '\n') inptr++; + if (*inptr != 0) + { + *inptr++ = 0; + if (*inptr == '\n') inptr++; + } -port = atoi(argv[argi++]); + if (strncmp(CS lineptr, CS outbuffer + 4, n - 4) != 0) + if (resp_optional) + { + inptr = lineptr; /* consume scriptline, not inputline */ + continue; + } + else + { + printf("<<< %s\n", lineptr); + printf("\n******** Input mismatch ********\n"); + exit(79); + } -/* Optional next arg is interface */ + /* Input matched script. Output the inputline, unless optional */ + DEBUG { printf("read matched\n"); fflush(stdout); } -if (argc > argi && - (isdigit((unsigned char)argv[argi][0]) || argv[argi][0] == ':')) - interface = argv[argi++]; + if (!resp_optional) + printf("<<< %s\n", lineptr); + else -/* Any more arguments are the name of a certificate file and key file */ + /* If there is further input after this line, consume inputline but not + scriptline in case there are several matching. Nonmatches are dealt with + above. */ -if (argc > argi) certfile = argv[argi++]; -if (argc > argi) keyfile = argv[argi++]; + if (*inptr != 0) + goto nextinput; +#ifdef HAVE_TLS + if (srv->sent_starttls) + { + if (lineptr[0] == '2') + { + unsigned int verify; -#if HAVE_IPV6 -/* For an IPv6 address, use an IPv6 sockaddr structure. */ + printf("Attempting to start TLS\n"); + fflush(stdout); -if (host_af == AF_INET6) - { - s_ptr = (struct sockaddr *)&s_in6; - s_len = sizeof(s_in6); - } -else -#endif +# ifdef HAVE_OPENSSL + srv->tls_active = tls_start(srv->sock, &srv->ssl, srv->ctx); +# endif -/* For an IPv4 address, use an IPv4 sockaddr structure, -even on an IPv6 system. */ +# ifdef HAVE_GNUTLS + { + int rc; + fd_set rfd; + struct timeval tv = { 0, 2000 }; - { - s_ptr = (struct sockaddr *)&s_in4; - s_len = sizeof(s_in4); - } + 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); -printf("Connecting to %s port %d ... ", address, port); + if (!srv->tls_active && !tls_quiet) printf("gnutls_handshake: %s\n", gnutls_strerror(rc)); -sock = socket(host_af, SOCK_STREAM, 0); -if (sock < 0) - { - printf("socket creation failed: %s\n", strerror(errno)); - exit(89); - } + /* look for an error on the TLS conn */ + FD_ZERO(&rfd); + FD_SET(srv->sock, &rfd); + if (select(srv->sock+1, &rfd, NULL, NULL, &tv) > 0) + { + retry2: + DEBUG { printf("call gnutls_record_recv\n"); fflush(stdout); } + rc = gnutls_record_recv(tls_session, CS inbuffer, bsiz - 1); + if (rc < 0) + { + DEBUG { printf("gnutls_record_recv: %s\n", gnutls_strerror(rc)); fflush(stdout); } + if (rc == GNUTLS_E_INTERRUPTED || rc == GNUTLS_E_AGAIN) + goto retry2; + if (!tls_quiet) printf("gnutls_record_recv: %s\n", gnutls_strerror(rc)); + srv->tls_active = FALSE; + } + DEBUG { printf("gnutls_record_recv: %d\n", rc); fflush(stdout); } + } + } +# endif /*HAVE_GNUTLS*/ + + if (!tls_quiet) + if (!srv->tls_active) + { + printf("Failed to start TLS\n"); + fflush(stdout); + } + +# ifdef HAVE_OPENSSL + else if (ocsp_stapling) + printf("Succeeded in starting TLS (with OCSP)\n"); +# endif -/* Bind to a specific interface if requested. On an IPv6 system, this has +# ifdef HAVE_GNUTLS + else if (ocsp_stapling) + { + if ((rc= gnutls_certificate_verify_peers2(tls_session, &verify)) < 0) + { + printf("Failed to verify certificate: %s\n", gnutls_strerror(rc)); + fflush(stdout); + } + else if (verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED)) + { + printf("Bad certificate\n"); + fflush(stdout); + } +# ifdef HAVE_GNUTLS_OCSP + else if (gnutls_ocsp_status_request_is_checked(tls_session, 0) == 0) + { + printf("Failed to verify certificate status\n"); + { + gnutls_datum_t stapling; + gnutls_ocsp_resp_t resp; + gnutls_datum_t printed; + if ( (rc= gnutls_ocsp_status_request_get(tls_session, &stapling)) == 0 + && (rc= gnutls_ocsp_resp_init(&resp)) == 0 + && (rc= gnutls_ocsp_resp_import(resp, &stapling)) == 0 + && (rc= gnutls_ocsp_resp_print(resp, GNUTLS_OCSP_PRINT_FULL, &printed)) == 0 + ) + { + fprintf(stderr, "%.4096s", printed.data); + gnutls_free(printed.data); + } + else + (void) fprintf(stderr,"ocsp decode: %s", gnutls_strerror(rc)); + } + fflush(stdout); + } + else + { + printf("OCSP status response: good signature\n"); + printf("Succeeded in starting TLS (with OCSP)\n"); + } +# endif /*HAVE_GNUTLS_OCSP*/ + } +# endif /*HAVE_GNUTLS*/ + + else + printf("Succeeded in starting TLS\n"); + } + else + printf("Abandoning TLS start attempt\n"); + } + srv->sent_starttls = 0; + #endif + } + + /* Wait for a bit before proceeding */ + + else if (strncmp(CS outbuffer, "+++ ", 4) == 0) + { + printf("%s\n", outbuffer); + sleep(atoi(CS outbuffer + 4)); + } + + /* Stack new input file */ + + else if (strncmp(CS outbuffer, "<<< ", 4) == 0) + { + FILE * new_f; + if (!(new_f = fopen((const char *)outbuffer+4 , "r"))) + { + printf("Unable to open '%s': %s", inptr, strerror(errno)); + exit(74); + } + do_file(srv, new_f, timeout, inbuffer, bsiz, inptr); + } + + + /* Send line outgoing, but barf if unconsumed incoming */ + + else + { + unsigned char * out = outbuffer; + + if (strncmp(CS outbuffer, ">>> ", 4) == 0) + { + crlf = 0; + out += 4; + n -= 4; + } + + if (*inptr != 0) + { + printf("Unconsumed input: %s", inptr); + printf(" About to send: %s\n", out); + exit(78); + } + + #ifdef HAVE_TLS + + /* Shutdown TLS */ + + if (strcmp(CS out, "stoptls") == 0 || + strcmp(CS out, "STOPTLS") == 0) + { + if (!srv->tls_active) + { + printf("STOPTLS read when TLS not active\n"); + exit(77); + } + printf("Shutting down TLS encryption\n"); + + #ifdef HAVE_OPENSSL + SSL_shutdown(srv->ssl); + SSL_free(srv->ssl); + #endif + + #ifdef HAVE_GNUTLS + gnutls_bye(tls_session, GNUTLS_SHUT_WR); + gnutls_deinit(tls_session); + tls_session = NULL; + gnutls_global_deinit(); + #endif + + srv->tls_active = 0; + continue; + } + + /* Remember that we sent STARTTLS */ + + srv->sent_starttls = (strcmp(CS out, "starttls") == 0 || + strcmp(CS out, "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(CS out, "starttls_wait") == 0) + { + out[8] = 0; + n = 8; + } + #endif + + printf(">>> %s\n", out); + if (crlf) + { + strcpy(CS out + n, "\r\n"); + n += 2; + } + + n = unescape_buf(out, n); + + /* OK, do it */ + + alarm(timeout); + if (srv->tls_active) + { + #ifdef HAVE_OPENSSL + rc = SSL_write (srv->ssl, out, n); + #endif + #ifdef HAVE_GNUTLS + if ((rc = gnutls_record_send(tls_session, CS out, n)) < 0) + { + printf("GnuTLS write error: %s\n", gnutls_strerror(rc)); + exit(76); + } + #endif + } + else + rc = write(srv->sock, out, n); + alarm(0); + + if (rc < 0) + { + printf("Write error: %s\n", strerror(errno)); + exit(75); + } + } + } +} + + + + +/************************************************* +* Main Program * +*************************************************/ + +const char * const HELP_MESSAGE = "\n\ +Usage: client\n" +#ifdef HAVE_TLS +"\ + [-tls-on-connect]\n\ + [-tls-quiet]\n\ + [-ocsp]\n" +# ifdef HAVE_GNUTLS +"\ + [-p priority-string]\n" +# endif +#endif +"\ + [-tn] n seconds timeout\n\ + \n\ + \n\ + []\n\ + []\n\ + []\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); + } +#ifdef HAVE_TLS + if (strcmp(argv[argi], "-tls-on-connect") == 0) + { + tls_on_connect = 1; + argi++; + } + else if (strcmp(argv[argi], "-tls-quiet") == 0) + { + tls_quiet = 1; + argi++; + } + 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. */ @@ -731,7 +1185,7 @@ if (interface != NULL) /* Bind */ - if (bind(sock, s_ptr, s_len) < 0) + if (bind(srv.sock, s_ptr, s_len) < 0) { printf("Unable to bind outgoing SMTP call to %s: %s", interface, strerror(errno)); @@ -745,14 +1199,30 @@ if (interface != NULL) #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; - s_in6.sin6_port = htons(port); 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 @@ -770,7 +1240,7 @@ else signal(SIGALRM, sigalrm_handler_crash); alarm(timeout); -rc = connect(sock, s_ptr, s_len); +rc = connect(srv.sock, s_ptr, s_len); save_errno = errno; alarm(0); @@ -779,11 +1249,18 @@ an externally applied timeout if the signal handler has been run. */ if (rc < 0) { - close(sock); + 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"); @@ -793,16 +1270,15 @@ printf("connected\n"); SSL_library_init(); SSL_load_error_strings(); -ctx = SSL_CTX_new(SSLv23_method()); -if (ctx == NULL) +if (!(srv.ctx = SSL_CTX_new(SSLv23_method()))) { printf ("SSL_CTX_new failed\n"); exit(84); } -if (certfile != NULL) +if (certfile) { - if (!SSL_CTX_use_certificate_file(ctx, certfile, SSL_FILETYPE_PEM)) + if (!SSL_CTX_use_certificate_file(srv.ctx, certfile, SSL_FILETYPE_PEM)) { printf("SSL_CTX_use_certificate_file failed\n"); exit(83); @@ -810,9 +1286,9 @@ if (certfile != NULL) printf("Certificate file = %s\n", certfile); } -if (keyfile != NULL) +if (keyfile) { - if (!SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM)) + if (!SSL_CTX_use_PrivateKey_file(srv.ctx, keyfile, SSL_FILETYPE_PEM)) { printf("SSL_CTX_use_PrivateKey_file failed\n"); exit(82); @@ -820,9 +1296,9 @@ if (keyfile != NULL) 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); +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 @@ -831,13 +1307,13 @@ SSL_CTX_set_info_callback(ctx, (void (*)())info_callback); #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_init(US certfile, US keyfile); tls_session = tls_session_init(); -#ifdef HAVE_OCSP +#ifdef HAVE_GNUTLS_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)sock); +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 @@ -859,7 +1335,7 @@ if (tls_on_connect) printf("Attempting to start TLS\n"); #ifdef HAVE_OPENSSL - tls_active = tls_start(sock, &ssl, ctx); + srv.tls_active = tls_start(srv.sock, &srv.ssl, srv.ctx); #endif #ifdef HAVE_GNUTLS @@ -870,309 +1346,33 @@ if (tls_on_connect) do { rc = gnutls_handshake(tls_session); } while (rc < 0 && gnutls_error_is_fatal(rc) == 0); - tls_active = rc >= 0; + srv.tls_active = rc >= 0; alarm(0); - if (!tls_active) printf("%s\n", gnutls_strerror(rc)); + if (!srv.tls_active) printf("%s\n", gnutls_strerror(rc)); } #endif - if (!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"); + if (!tls_quiet) + if (!srv.tls_active) + printf("Failed to start TLS\n"); +#if defined(HAVE_GNUTLS) && defined(HAVE_GNUTLS_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"); + else + printf("Succeeded in starting TLS%s\n", ocsp_stapling ? " (with OCSP)":""); } #endif -while (fgets(CS outbuffer, sizeof(outbuffer), stdin) != NULL) - { - int n = (int)strlen(CS outbuffer); - - /* Strip trailing newline */ - if (outbuffer[n-1] == '\n') outbuffer[--n] = 0; - - /* Expect incoming */ - - if ( strncmp(CS outbuffer, "???", 3) == 0 - && (outbuffer[3] == ' ' || outbuffer[3] == '*') - ) - { - unsigned char *lineptr; - unsigned exp_eof = outbuffer[3] == '*'; - - 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(81); - } - else if (rc == 0) - if (exp_eof) - { - printf("Expected EOF read\n"); - continue; - } - else - { - printf("Enexpected EOF read\n"); - close(sock); - exit(80); - } - else if (exp_eof) - { - printf("Expected EOF not read\n"); - close(sock); - exit(74); - } - 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(CS lineptr, CS outbuffer + 4, (int)strlen(CS outbuffer) - 4) != 0) - { - printf("\n******** Input mismatch ********\n"); - exit(79); - } - - #ifdef HAVE_TLS - if (sent_starttls) - { - if (lineptr[0] == '2') - { -int rc; - unsigned int verify; - - printf("Attempting to start TLS\n"); - fflush(stdout); - - #ifdef HAVE_OPENSSL - tls_active = tls_start(sock, &ssl, 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); - tls_active = rc >= 0; - alarm(0); - - if (!tls_active) printf("%s\n", gnutls_strerror(rc)); - } - #endif - - if (!tls_active) - { - printf("Failed to start TLS\n"); - fflush(stdout); - } - #ifdef HAVE_GNUTLS - else if (ocsp_stapling) - { - if ((rc= gnutls_certificate_verify_peers2(tls_session, &verify)) < 0) - { - printf("Failed to verify certificate: %s\n", gnutls_strerror(rc)); - fflush(stdout); - } - else if (verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED)) - { - printf("Bad certificate\n"); - fflush(stdout); - } - #ifdef HAVE_OCSP - else if (gnutls_ocsp_status_request_is_checked(tls_session, 0) == 0) - { - printf("Failed to verify certificate status\n"); - { - gnutls_datum_t stapling; - gnutls_ocsp_resp_t resp; - gnutls_datum_t printed; - if ( (rc= gnutls_ocsp_status_request_get(tls_session, &stapling)) == 0 - && (rc= gnutls_ocsp_resp_init(&resp)) == 0 - && (rc= gnutls_ocsp_resp_import(resp, &stapling)) == 0 - && (rc= gnutls_ocsp_resp_print(resp, GNUTLS_OCSP_PRINT_FULL, &printed)) == 0 - ) - { - fprintf(stderr, "%.4096s", printed.data); - gnutls_free(printed.data); - } - else - (void) fprintf(stderr,"ocsp decode: %s", gnutls_strerror(rc)); - } - fflush(stdout); - } - #endif - } - #endif - 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(CS outbuffer, "+++ ", 4) == 0) - { - printf("%s\n", outbuffer); - sleep(atoi(CS 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(78); - } - - #ifdef HAVE_TLS - - /* Shutdown TLS */ - - if (strcmp(CS outbuffer, "stoptls") == 0 || - strcmp(CS outbuffer, "STOPTLS") == 0) - { - if (!tls_active) - { - printf("STOPTLS read when TLS not active\n"); - exit(77); - } - 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(CS outbuffer, "starttls") == 0 || - strcmp(CS 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(CS outbuffer, "starttls_wait") == 0) - { - outbuffer[8] = 0; - n = 8; - } - #endif - - printf(">>> %s\n", outbuffer); - strcpy(CS outbuffer + n, "\r\n"); - - /* Turn "\n" and "\r" into the relevant characters. This is a hack. */ - - while ((escape = US strstr(CS outbuffer, "\\r")) != NULL) - { - *escape = '\r'; - memmove(escape + 1, escape + 2, (n + 2) - (escape - outbuffer) - 2); - n--; - } - - while ((escape = US strstr(CS 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(76); - } - #endif - } - else - { - rc = write(sock, outbuffer, n + 2); - } - alarm(0); - - if (rc < 0) - { - printf("Write error: %s\n", strerror(errno)); - exit(75); - } - } - } +do_file(&srv, stdin, timeout, inbuffer, sizeof(inbuffer), inptr); printf("End of script\n"); -shutdown(sock, SHUT_WR); -while ((rc = read(sock, inbuffer, sizeof(inbuffer))) > 0) ; -close(sock); +shutdown(srv.sock, SHUT_WR); +if (fcntl(srv.sock, F_SETFL, O_NONBLOCK) == 0) + while (read(srv.sock, inbuffer, sizeof(inbuffer)) > 0) ; +close(srv.sock); exit(0); }