OpenSSL-Dev'ers -

Finding the #ifndef WINDOWS in apps/s_client.c prevented me from
using the otherwise-oh-so-complete feature set of this application
on my OS 'of choice' I decided that, instead of learning to work
around or through the peculiarities and special feeding of WINSOCK,
it would be a far, far more interesting exercise to code up a simple
in-line client that 'knew' HTTP, at least in a rudimentary fashion,
after the model of the also-oh-so-useful MKS Toolkit 'web' command.
Hence the enclosed utility, gratuitously cloned from the s_client.c 
and s_socket.c sources with a few comments of my own sprinkled
lightly here and there.  See the verbose, self-serving comment after
the OpenSSL Copyright if you're interested in more details.  As always,
comments/insights/flaming onions from the dev team and others are
always welcome (well, virtual onions anyway). 

TT

/* sweb.c */
/* Written by Tom Titchener ([EMAIL PROTECTED]) for the OpenSSL
 * project 1999.
 */
/* ====================================================================
 * Copyright (c) 1999 The OpenSSL Project.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by the OpenSSL Project
 *    for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)"
 *
 * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For written permission, please contact
 *    [EMAIL PROTECTED]
 *
 * 5. Products derived from this software may not be called "OpenSSL"
 *    nor may "OpenSSL" appear in their names without prior written
 *    permission of the OpenSSL Project.
 *
 * 6. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the OpenSSL Project
 *    for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)"
 *
 * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE OpenSSL PROJECT OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 *
 * This product includes cryptographic software written by Eric Young
 * ([EMAIL PROTECTED]).  This product includes software written by Tim
 * Hudson ([EMAIL PROTECTED]).
 *
 */
/* This application was created by copying source from the OpenSSL
 * files apps/s_client.c and apps/s_socket.c.  It implements a simple
 * SSL client with many of the same options as the s_client app, with
 * the difference that this client sends a single HTTP command, and
 * displays the HTTP command results, whereas s_client operates in a
 * loop and functions as an SSL pipe, echoing input to the server and
 * returning output from the server.  The application is modeled after
 * the MKS Toolkit "web" command, which performs the same functions 
 * (and more) over a TCP connection (but not over an SSL connection).
 *
 * I think of the utility as a hybrid combining features (and source)
 * from s_client and command-line (and conception) from the MKS Toolkit
 * "web" command.  We use it at CertCo to script SSL interactions with 
 * our web servers.  I believe it also could serve as a convenient 
 * example of an OpenSSL client, demonstrating the basic API and the
 * many ways the programmer can customize the SSL connection.  
 *
 * To that end, I've added short in-line comments describing the purpose 
 * and behavior of the APIs invoked below.  Finally, the utility provides
 * a client that can be used on Windows, where WinSock peculiarities 
 * (i.e. (if I understand them properly) the fact that all fds in the
 * arguments must be for sockets) don't allow the select() loop design 
 * of the s_client application.  Note that this application has been
 * developed, debugged, and tested on Windows/NT only.  
 *
 * The easiest way to build this application is to copy sweb.c to the
 * apps directory, create a new line "TEST=sweb.c", regenerate the 
 * makefiles (for Windows/NT), and then build everything.
 *
 * Note: the delimiter for specifying multiple headers is the new-line,
 * which means you can't use the DOS shell to specify an input argument
 * with multiple headers.  With a unix-like shell, you can use the
 * single-quotes to capture the newlines in the input argument.
 */
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <string.h>
#ifdef WIN32
#include <winsock.h>
#endif
#include "bio.h"
#include "asn1.h"
#include "x509.h"
#include "ssl.h"
#include "err.h"
#include "pem.h"

#ifndef BUFSIZ
#define BUFSIZ 1024
#endif

#define HTTP_VERSION "HTTP/1.0"

/* Passphrase for secret key, value from -p command-line argument.
 * Needs global scope to be used by pass_cb callback.
 */
static char* pass=NULL;

/* Error code output from verify callback verify_cb.  Should be
 * checked after SSL_connect() failure.  See verify_cb.
 */
static int verify_error=X509_V_OK;
/* Ignore errors verifying certificates down to this deep in the 
 * chain.  For this depth and deeper, set verify_error to "chain
 * too long" and return 0 so x509_vfy.c:internal_verify() stops
 * processing the chain.  Set from value for command-line switch 
 * -v.  See verify_cb.
 */
static int verify_depth=0;

#ifdef WIN32
#define get_last_socket_error() WSAGetLastError()
#else
#define get_last_socket_error() errno
#endif

#ifdef WIN32
static struct WSAData wsa_state;

static void sock_cleanup() {
  WSACancelBlockingCall();
  WSACleanup();
}
#endif

/* Debug callback tracks SSL connection states and errors.  
 * Initialized by SSL_CTX_set_info_callback.  Invoked when
 * -d argument value is >= 3.  Copied from apps/s_cb.c.
 */
static void info_cb(SSL *s,int where,int ret) {
  char *str="undefined";
  int w=where& ~SSL_ST_MASK;
  if (w & SSL_ST_CONNECT) str="SSL_connect";
  else if (w & SSL_ST_ACCEPT) str="SSL_accept";
  if (where & SSL_CB_LOOP) 
    fprintf(stderr,"%s:%s\n",str,SSL_state_string_long(s));
  else if (where & SSL_CB_ALERT) {
    str=(where & SSL_CB_READ)?"read":"write";
    fprintf(stderr,"SSL3 alert %s:%s:%s\n",
            str,
            SSL_alert_type_string_long(ret),
            SSL_alert_desc_string_long(ret));
  }
  else if (where & SSL_CB_EXIT) {
    if (ret == 0)
      fprintf(stderr,"%s:failed in %s\n",str,SSL_state_string_long(s));
    else if (ret < 0) 
      fprintf(stderr,"%s:error in %s\n",str,SSL_state_string_long(s));
  }
}

/* Debug callback tracks raw SSL buffers.  Initialized by 
 * BIO_set_callback().  Invoked when debug level is >= 4.
 * Copied from apps/s_cb.c.  IMHOP, BIO piggybacking like 
 * this is one of Eric's coolest SW designs.
 */
static long bio_dump_cb(BIO *bio, int cmd, char *argp,
                        int argi, long argl, long ret) {
  BIO *out;
  out=(BIO *)BIO_get_callback_arg(bio);
  if (out == NULL) return(ret);
  if (cmd == (BIO_CB_READ|BIO_CB_RETURN)) {
    BIO_printf(out,"read from %08X [%08lX] (%d bytes => %ld (0x%X))\n",
               bio,argp,argi,ret,ret);
    BIO_dump(out,argp,(int)ret);
    return(ret);
  }
  else if (cmd == (BIO_CB_WRITE|BIO_CB_RETURN)) {
    BIO_printf(out,"write to %08X [%08lX] (%d bytes => %ld (0x%X))\n",
               bio,argp,argi,ret,ret);
    BIO_dump(out,argp,(int)ret);
  }
  return(ret);
}

/* Verify certificate callback.  Gets invoked by crypto/x509/x509_vfy.c:
 * internal_verify() after it checks the signature and the time of 
 * the certificate.  Copied from apps/s_cb.c.  Configured through 
 * verify_depth and verify_error, initialized in main().  Doesn't
 * really do much other than print verify errors to the screen and
 * ignore errors down to verify_depth.  Mainly an example of how
 * OpenSSL let's your app have a say in certificate validating.
 * At this point OpenSSL has checked the signature and dates are 
 * good, so you don't need to do that.  One thing you could do 
 * here would be to verify the certificate status with an on-line
 * service, e.g. via OCSP.
 */
static int verify_cb(int ok, X509_STORE_CTX *ctx) {
  char buf[256];
  X509 *err_cert;
  int err,depth;
  BIO* bio_err;

  if ((bio_err=BIO_new(BIO_s_file())) == NULL) 
    return(0);
  BIO_set_fp(bio_err,stderr,BIO_NOCLOSE);

  err_cert=X509_STORE_CTX_get_current_cert(ctx);
  err=X509_STORE_CTX_get_error(ctx);
  depth=X509_STORE_CTX_get_error_depth(ctx);

  X509_NAME_oneline(X509_get_subject_name(err_cert),buf,256);
  BIO_printf(bio_err,"depth=%d %s\n",depth,buf);
  if (!ok) {
    BIO_printf(bio_err,"verify error:num=%d:%s\n",err,
               X509_verify_cert_error_string(err));
    if (depth > verify_depth) 
      verify_error=X509_V_ERR_CERT_CHAIN_TOO_LONG;
    else {
      ok=1;
      verify_error=X509_V_OK;
    }
  }
  switch (ctx->error) {
  case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
    X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert),buf,256);
    BIO_printf(bio_err,"issuer= %s\n",buf);
    break;
  case X509_V_ERR_CERT_NOT_YET_VALID:
  case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
    BIO_printf(bio_err,"notBefore=");
    ASN1_TIME_print(bio_err,X509_get_notBefore(ctx->current_cert));
    BIO_printf(bio_err,"\n");
    break;
  case X509_V_ERR_CERT_HAS_EXPIRED:
  case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
    BIO_printf(bio_err,"notAfter=");
    ASN1_TIME_print(bio_err,X509_get_notAfter(ctx->current_cert));
    BIO_printf(bio_err,"\n");
    break;
  }
  BIO_printf(bio_err,"verify return:%d\n",ok);
  BIO_free(bio_err);
  return(ok);
}

/* Passphrase callback.  Initialized by SSL_CTX_set_default_passwd_cb.
 * Used by various functions in ssl/ssl_rsa.c.  Just copies passphrase
 * initialized by main() to return buffer.  If you don't specify this
 * and the RSA key is encrypted, the PEM libraries prompt for it at the
 * terminal.
 */
static int pass_cb(char *buf,int len, int verify) {
  int i;
  if (pass == NULL) return(0);
  i=strlen(pass);
  i=(i > len)?len:i;
  memcpy(buf,pass,i);
  return(i);
}

/* Who am I?  What am I doing here? */
static char *usage[]={
"Usage: sweb  [-d <1..4>] [-v <0..level>] [-h header] [-c cert] [-k key]\n",
"       [-p pass] [-s <1..4>] ...\n",
"        ... get url\n",
"        ... put url file\n",
"        ... post url file\n",
"SSL_CIPHER, SSL_CA_PATH, SSL_CA_FILE environment variables\n",
NULL
};

/* One humongous, in-line straight pass through SSL and (rudimentary)
 * HTTP request/response processing.  Almost all code copied from 
 * apps/s_client.c (although the comments are mine:)).
 */
int main(int argc, char **argv) {
  FILE* fp;
  STACK* sk;
  X509_NAME *xn;
  SSL* con=NULL;
  SSL_CIPHER *sc;
  X509 *peer=NULL;
  unsigned long a;
  SSL_CTX *ctx=NULL;
  struct stat stbuf;
  struct hostent* he;
  unsigned int in[4];
  unsigned char ip[4];
  SSL_METHOD *meth=NULL;
  struct sockaddr_in sa;
  BIO* sbio=NULL,* ebio,* dbio;
  int s,i,j,l,v=0, d=0, z=3, get=0, put=0, post=0, port=443, badops=0;
  char* head=NULL,* cert=NULL,* key=NULL,* file=NULL,* url=NULL,* cmd=NULL,
      * ciph=NULL,* targ=NULL,* cafile=NULL,* capath=NULL,* p,* q,* m,** pp,
      host[BUFSIZ], msg[BUFSIZ], buf[BUFSIZ];

  argc--; argv++;
  while (argc >= 1) {
    if (strcmp(*argv,"-c") == 0) {
      if (--argc < 1) goto bad;
      cert=*(++argv);
    } else if (strcmp(*argv,"-h") == 0) {
      if (--argc < 1) goto bad;
      head=*(++argv);
    } else if (strcmp(*argv,"-p") == 0) {
      if (--argc < 1) goto bad;
      pass=*(++argv);
    } else if (strcmp(*argv,"-s") == 0) {
      if (--argc < 1) goto bad;
      z=atoi(*(++argv));
      if (z < 1 || z > 4) {
        fprintf(stderr,"sweb: -s value %d not 1, 2, 3, or 4\n", z);
        badops++;
      }
    } else if (strcmp(*argv,"-k") == 0) {
      if (--argc < 1) goto bad;
      key=*(++argv);
    } else if (strcmp(*argv,"-v") == 0) {
      if (--argc < 1) goto bad;
      v=verify_depth=atoi(*(++argv));
    } else if (strcmp(*argv,"-d") == 0) {
      if (--argc < 1) goto bad;
      d=atoi(*(++argv));
    } else if (stricmp(*argv,"get") == 0) {
      if (--argc < 1) goto bad;
      url=p=q=*(++argv);
      cmd="GET";
      get=1;
    } else if (stricmp(*argv,"put") == 0) {
      if (--argc < 1) goto bad;
      url=*(++argv);
      if (--argc < 1) goto bad;
      file=*(++argv);
      put=1;
      cmd="PUT";
    } else if (stricmp(*argv,"post") == 0) {
      if (--argc < 1) goto bad;
      url=*(++argv);
      if (--argc < 1) goto bad;
      file=*(++argv);
      post=1;
      cmd="POST";
    } else {
bad:
      fprintf(stderr,"sweb: Unknown option %s\n",*argv);
      badops=1;
      break;
    }
    argc--;
    argv++;
  }
  switch (put + get + post) {
  case 0:
    fprintf(stderr,"sweb: Missing required argument put, get, or post\n");
    badops++;
    break;
  case 1:
    break;
  default:
    fprintf(stderr,"sweb: Too many operations, choose on of put, get, or post\n"); 
    badops++;
    break;
  }
  if (badops) {
    for (pp=usage; (*pp != NULL); pp++)
      fprintf(stderr,*pp);
    goto err;
  }
  /* No key file?  Try pointing at cert file.  With PEM you can simply
   * concatenate cert and keys and libraries will find correct header.
   */
  if (key == NULL)
    key=cert;

  /* Connect to server. */
  /* Isolate host name and optional port from URL: https://<host[:port]>/foo.
   * With no :port specifier, we default to 443.
   */
  if (strnicmp(url,"https://",8)) {
    fprintf(stderr,"sweb: url \"%s\": not a valid https hostname\n", url);
    goto err;
  }
  p=url+8;
  if (!(q=strchr(p,'/'))) {
    fprintf(stderr,"sweb: url \"%s\": not a valid https hostname\n", url);
    goto err;
  }
  memcpy(host,p,q-p);
  host[q-p]='\0';
  targ=q;
  if ((q=strchr(host,':'))) {
    port=atoi(q+1);
    q='\0';
  }
#ifdef WIN32
#ifdef SIGINT
  signal(SIGINT,(void (*)(int))sock_cleanup);
#endif
  memset(&wsa_state,0,sizeof(wsa_state));
  if (WSAStartup(0x0101,&wsa_state)!=0) {
    i=WSAGetLastError();
    fprintf(stderr,"sweb: Unable to start WINSOCK, error code=%d\n", i);
    goto err;
  }
#endif
  /* Get IP address for host from URL.  Allow for dot format IP address. */
  if (sscanf(host,"%d.%d.%d.%d",&(in[0]),&(in[1]),&(in[2]),&(in[3])) == 4) {
    for (i=0; i<4; i++)
      if (in[i] > 255) {
        fprintf(stderr,"sweb: Invalid IP address %s\n", host);
        goto err;
      }
    ip[0]=in[0];ip[1]=in[1];ip[2]=in[2];ip[3]=in[3];
  }
  else {
    if (!(he=gethostbyname(host))) {
      fprintf(stderr,"sweb: gethostbyname failure\n");
      goto err;
    }
    if ((short)he->h_addrtype != AF_INET) {
      fprintf(stderr,"sweb: gethostbyname addr is not AF_INET\n");
      goto err;
    }
    ip[0]=he->h_addr_list[0][0];ip[1]=he->h_addr_list[0][1];
    ip[2]=he->h_addr_list[0][2];ip[3]=he->h_addr_list[0][3];
  }
  if (d >= 1) 
    fprintf(stdout,"sweb: host: %s, ip: %d.%d.%d.%d, port: %d\n", 
            host, ip[0], ip[1], ip[2], ip[3], port);
  /* Get a socket and connect to a server at the host and port number
   * from the URL.
   */
  memset((char *)&sa,0,sizeof(sa));
  sa.sin_family=AF_INET;
  sa.sin_port=htons((unsigned short)port);
  a=(unsigned long)((unsigned long)ip[0]<<24L)|
                   ((unsigned long)ip[1]<<16L)|
                   ((unsigned long)ip[2]<< 8L)|
                   ((unsigned long)ip[3]);
  sa.sin_addr.s_addr=htonl(a);
  if ((s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) == INVALID_SOCKET) {
    perror("socket");
    goto err;
  }
  i=0;
  if ((i=setsockopt(s,SOL_SOCKET,SO_KEEPALIVE,(char *)&i,sizeof(i))) < 0) {
    perror("keepalive");
    goto err;
  }
  if (connect(s,(struct sockaddr *)&sa,sizeof(sa)) == -1) {
    perror("connect"); 
    goto err;
  }

  /* Initialize OpenSSL system. */
  /* Initialize cipher and digest subsystems according to OpenSSL
   * compile-time definitions (NO_RSA, NO_IDEA, NO_SHA1, NO_DSA, etc.).
   * If your application suddenly starts complaining that it doesn't
   * recognize OIDs like md5WithRSAEncryption etc., it's generally
   * because you forgot to call this function.
   */
  SSLeay_add_all_algorithms();
  /* Make sure ERR_error_string() in ERR_print_errors() and its 
   * brethren show you nice shiny text instead of dull old numbers.
   */
  SSL_load_error_strings();

  /* Create and configure SSL_CTX. */
  /* Initialize protocol version, default is ssl3, can choose 23, 2,
   * or TLSv1 according to value for -s command-line argument.  
   * SSL_CTX_new() does not set a default for NULL SSL_METHOD* 
   * argument.  You must supply one of these methods.
   */
  switch (z) {
  case 1:
    meth=SSLv23_client_method();
    break;
  case 2:
    meth=SSLv2_client_method();
    break;
  case 3:
    meth=SSLv3_client_method();
    break;
  case 4:
    meth=TLSv1_client_method();
    break;
  }
  if (!(ctx=SSL_CTX_new(meth))) {
    fprintf(stderr, "sweb: Cannot create new SSL context\n");
    ERR_print_errors_fp(stderr);
    goto err;
  }
  /* Info callback just prints SSL states and errors. */
  if (d >= 3) 
    SSL_CTX_set_info_callback(ctx,info_cb);
  /* Verify callback prints errors, ignores 'em up to verify_depth. */
  if (v)
    SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_cb);
  /* Initialize certificate store.  Used by ssl/s[2|3]_[clnt|srvr].c,
   * which call ssl/ssl_cert.c:ssl_verify_cert_chain(), which calls
   * crypto/x509/x509_vfy.c:X509_verify_cert(), which calls
   * crypto/x509/x509_lu.c:X509_STORE_get_by_subject().  On Windows,
   * SSL_CA_FILE contains concatenated CA certs.  On UNIX, SSL_CA_PATH
   * points a a directory with symlinks to the certs where the symlink
   * names are hash values from the target certs.  If you want to 
   * see verify_cb() happily telling you about the certs in the chain 
   * with no errors, point SSL_CA_FILE and SSL_CA_PATH at a file with 
   * the cert(s) for the CA(s) that signed the server's cert.
   */
  cafile = getenv("SSL_CA_FILE");
  capath = getenv("SSL_CA_PATH");
  if (cafile || capath) {
    if (!SSL_CTX_load_verify_locations(ctx, cafile, capath)) {
      fprintf(stderr,"sweb: Cannot load verify locations %s and %s\n",
              cafile, capath);
      ERR_print_errors_fp(stderr);
    }
    if (d >= 1)
      fprintf(stderr,"using ca file %s/%s\n", capath, cafile);
  }
  SSL_CTX_set_default_verify_paths(ctx);
  /* Point the SSL_CTX at a certificate and key file (if provided).
   * That way, if the server asks for a certificate, the client will
   * have one to send (and a key to generate the certificate verify 
   * message).  Call SSL_CTX_check_private_key to make sure the key
   * in the certificate matches they key in the private key file.
   */
  if (cert && (SSL_CTX_use_certificate_file(ctx,cert,SSL_FILETYPE_PEM) <= 0)) {
    fprintf(stderr,"sweb: Unable to get certificate from '%s'\n",cert);
    ERR_print_errors_fp(stderr);
    goto err;
  }
  if (pass)
    SSL_CTX_set_default_passwd_cb(ctx,pass_cb);
  if (key && (SSL_CTX_use_PrivateKey_file(ctx,key,SSL_FILETYPE_PEM) <= 0)) {
    fprintf(stderr,"sweb: Unable to get private key from '%s'\n",key);
    ERR_print_errors_fp(stderr);
    goto err;
  }
  if ((cert || key) && !SSL_CTX_check_private_key(ctx)) {
    fprintf(stderr,"sweb: Private key does not match certificate public key\n");
    goto err;
  }

  /* Create and configure SSL connection. */

  if (!(con=(SSL *)SSL_new(ctx))) {
    fprintf(stderr, "sweb: Cannot create new SSL connection\n");
    ERR_print_errors_fp(stderr);
    goto err;
  }
  /* Use OpenSSL ciphers -v to see the cipher strings and their SSL
   * versions, key exchange, authentication, encryption, and message
   * digest algorithms, and key length restrictions.  See the OpenSSL
   * for the syntax to combine ciphers, e.g. !SSLv2:RC4-MD5:RC4-SHA.
   * If you don't specify anything, you get the same as "DEFAULT", which
   * means "ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP".
   */
  if ((ciph=getenv("SSL_CIPHER")))
    if (!SSL_CTX_set_cipher_list(ctx,ciph)) {
      fprintf(stderr, "sweb: Cannot use cipher list %s\n", ciph);
      goto err;
    }
  /* Ordinarily, just decorate the SSL connection with the socket file
   * descriptor.  But for super-cool shazaam debugging, build your
   * own socket BIO, decorate it with a BIO debugging callback, and
   * presto, see a dump of the bytes as they fly.  See ssl/ssl_lib.c
   * for details.  
   */
  if (d < 4) 
    SSL_set_fd(con,s);
  else {
    if (!(sbio=BIO_new_socket(s,BIO_NOCLOSE))) {
      fprintf(stderr, "sweb: Cannot create new socket BIO\n");
      ERR_print_errors_fp(stderr);
      goto err;
    }
    SSL_set_bio(con,sbio,sbio);
    con->debug=1;
    dbio=BIO_new_fp(stdout,BIO_NOCLOSE);
    BIO_set_callback(sbio,bio_dump_cb);
    BIO_set_callback_arg(sbio,dbio);
  }
  /* Initialize the state of the connection so the first i/o operation
   * knows to do the SSL connect.  Strictly speaking, this not necessary,
   * as this code goes ahead and calls SSL_connect() anyway (so I can 
   * put the connect tracing stuff in one handy spot).  But look closely
   * in ssl/s_client.c for a call to SSL_connect.  You won't find one.
   */
  SSL_set_connect_state(con);

  /* Now that we've finally finished customizing the context and the
   * connection, go ahead and see if it works.  This function call 
   * invokes all the SSL connection handshake and key exchange logic,
   * (which is why there's so much to report on after it completes).
   */
  l = SSL_connect(con);
  switch (SSL_get_error(con,l)) { 
  case SSL_ERROR_NONE:
    break;
  case SSL_ERROR_SYSCALL:
    if ((l != 0) && (i = get_last_socket_error()))
      fprintf(stderr,"sweb: Write errno=%d\n", i);
    /* fall through */
  case SSL_ERROR_WANT_WRITE:
  case SSL_ERROR_WANT_READ:
  case SSL_ERROR_WANT_X509_LOOKUP:
  case SSL_ERROR_ZERO_RETURN:
  case SSL_ERROR_SSL:
  default:
    ERR_print_errors_fp(stderr);
    goto err;
    break;
  }
  /* Report on what happened now that we've successfully connected. */
  if (d >= 3) {
    if ((ebio=BIO_new(BIO_s_file())) == NULL) {
      fprintf(stderr,"sweb: Cannot create new BIO\n");
      ERR_print_errors_fp(stderr);
      goto err;
    }
    BIO_set_fp(ebio,stderr,BIO_NOCLOSE);
    if ((sk=SSL_get_peer_cert_chain(con)) != NULL) {
      BIO_printf(ebio,"---\nCertificate chain\n");
      for (i=0; i<sk_num(sk); i++) {
        X509_NAME_oneline(X509_get_subject_name((X509*)sk_value(sk,i)),buf,BUFSIZ);
        BIO_printf(ebio,"%2d s:%s\n",i,buf);
        X509_NAME_oneline(X509_get_issuer_name((X509 *)sk_value(sk,i)),buf,BUFSIZ);
        BIO_printf(ebio,"   i:%s\n",buf);
      }
    }
    BIO_printf(ebio,"---\n");
    if ((peer=SSL_get_peer_certificate(con)) != NULL) {
      BIO_printf(ebio,"Server certificate\n");
      PEM_write_bio_X509(ebio,peer);
      X509_NAME_oneline(X509_get_subject_name(peer),buf,BUFSIZ);
      BIO_printf(ebio,"subject=%s\n",buf);
      X509_NAME_oneline(X509_get_issuer_name(peer),buf,BUFSIZ);
      BIO_printf(ebio,"issuer=%s\n",buf);
    }
    else
      BIO_printf(ebio,"no peer certificate available\n");
    if (((sk=SSL_get_client_CA_list(con)) != NULL) && (sk_num(sk) > 0)) {
      BIO_printf(ebio,"---\nAcceptable client certificate CA names\n");
      for (i=0; i<sk_num(sk); i++) {
        xn=(X509_NAME *)sk_value(sk,i);
        X509_NAME_oneline(xn,buf,sizeof(buf));
        BIO_write(ebio,buf,strlen(buf));
        BIO_write(ebio,"\n",1);
      }
    }
    else {
      BIO_printf(ebio,"---\nNo client certificate CA names sent\n");
    }
    if ((p=SSL_get_shared_ciphers(con,buf,BUFSIZ)) != NULL) {
      BIO_printf(ebio,"---\nCiphers common between both SSL endpoints:\n");
      j=i=0;
      while (*p) {
        if (*p != ':') {
          BIO_write(ebio,p,1);j++;
        } else {
          BIO_write(ebio,"                ",15-j%25);i++;j=0;
          BIO_write(ebio,((i%3)?" ":"\n"),1);
        }
        p++;
      }
      BIO_write(ebio,"\n",1);
    }
    BIO_printf(ebio,
               "---\nSSL handshake has read %ld bytes and written %ld bytes\n",
               BIO_number_read(SSL_get_rbio(con)),
               BIO_number_written(SSL_get_wbio(con)));
    BIO_printf(ebio,((con->hit)?"---\nReused, ":"---\nNew, "));
    sc=SSL_get_current_cipher(con);
    BIO_printf(ebio,"%s, Cipher is %s\n",
               SSL_CIPHER_get_version(sc),SSL_CIPHER_get_name(sc));
    if (peer != NULL) {
      EVP_PKEY *pktmp;
      pktmp = X509_get_pubkey(peer);
      BIO_printf(ebio,"Server public key is %d bit\n", EVP_PKEY_bits(pktmp));
      EVP_PKEY_free(pktmp);
    }
    SSL_SESSION_print(ebio,SSL_get_session(con));
    BIO_printf(ebio,"---\n");
    if (peer != NULL)
      X509_free(peer);
    BIO_free(ebio);
  }

  /* Prepare HTTP message:  request line first. */
  sprintf(msg, "%s %s %s\r\n", cmd, targ, HTTP_VERSION);
  /* HTTP request headers, new-line delimited. */
  m=&msg[strlen(msg)];
  if ((p=head)) {
    while ((q=strchr(p,'\n'))) {
      sprintf(m, "%.*s\r\n", q-p, p);
      m=&msg[strlen(msg)];
      p=q+1;
    }
    sprintf(m, "%s\r\n", p);
    m=&msg[strlen(msg)];
  }
  /* Post needs content-length. */
  if (post) {
    if (stat(file, &stbuf) < 0) {
      perror(file);
      fprintf(stderr, "sweb: Cannot stat file %s\n", file);
      goto err;
    }
    l = stbuf.st_size;
    sprintf(m, "Content-Length: %d\r\n", l);
    m=&msg[strlen(msg)];
  }
  /* Empty line separates request and headers from body. */
  sprintf(m, "\r\n");
  m=&msg[strlen(msg)];
  if (d >= 2) 
    fprintf(stderr,"write: %s\n", msg);
  l=SSL_write(con,msg,strlen(msg));
  switch (SSL_get_error(con,l)) { 
  case SSL_ERROR_NONE:
    break;
  case SSL_ERROR_SYSCALL:
    if ((l != 0) && (i = get_last_socket_error()))
      fprintf(stderr,"sweb: Write errno=%d\n", i);
    /* fall through */
  case SSL_ERROR_WANT_WRITE:
  case SSL_ERROR_WANT_READ:
  case SSL_ERROR_WANT_X509_LOOKUP:
  case SSL_ERROR_ZERO_RETURN:
  case SSL_ERROR_SSL:
  default:
    ERR_print_errors_fp(stderr);
    goto err;
    break;
  }
  /* For post, send body. */
  if (post) {
    /* On Windows, mode must be "rb", else amount you send
     * is less than length returned by stat for Content-Length
     * header.
     */
    if (!(fp = fopen(file, "rb"))) {
      perror(file);
      fprintf(stderr, "sweb: Cannot open file %s\n", file);
      goto err;
    }
    while ((l=fread(buf,1,sizeof buf,fp)) > 0) {
      if (d >= 2) 
        fprintf(stderr,"write %d bytes: %.*s\n", l, l, buf);
      l=SSL_write(con,buf,l);
      switch (SSL_get_error(con,l)) { 
      case SSL_ERROR_NONE:
        break;
      case SSL_ERROR_SYSCALL:
        if ((l != 0) && (i = get_last_socket_error()))
          fprintf(stderr,"sweb: Write errno=%d\n", i);
        /* fall through */
      case SSL_ERROR_WANT_WRITE:
      case SSL_ERROR_WANT_READ:
      case SSL_ERROR_WANT_X509_LOOKUP:
      case SSL_ERROR_ZERO_RETURN:
      case SSL_ERROR_SSL:
      default:
        ERR_print_errors_fp(stderr);
        goto err;
      }
    }
    fclose(fp);
    if (l < 0) {
      perror(file);
      fprintf(stderr, "sweb: Cannot read from file %s\n", file);
      goto err;
    }
  }

  /* Get and display response. */
  while ((l = SSL_read(con, buf, sizeof buf)) > 0) {
    fwrite(buf, l, 1, stdout);
  }
  switch (SSL_get_error(con,l)) {
  case SSL_ERROR_NONE:
    break;
  case SSL_ERROR_SYSCALL:
    if ((i = get_last_socket_error()))
      fprintf(stderr,"sweb: Read errno=%d\n", i);
    /* fall through */
  case SSL_ERROR_WANT_WRITE:
  case SSL_ERROR_WANT_READ:
  case SSL_ERROR_WANT_X509_LOOKUP:
  case SSL_ERROR_ZERO_RETURN:
  case SSL_ERROR_SSL:
    ERR_print_errors_fp(stderr);
    goto err;
  }

  /* Connection cleanup */
  SSL_shutdown(con);
  SSL_free(con);
  /* Context cleanup */
  SSL_CTX_free(ctx);
  if (sbio) 
    BIO_free(sbio);
  return 1;
err:
  return 0;
}

______________________________________________________________________
OpenSSL Project                                 http://www.openssl.org
Development Mailing List                       [EMAIL PROTECTED]
Automated List Manager                           [EMAIL PROTECTED]

Reply via email to