------------------------------------------------------------ revno: 13360 revision-id: squid3@treenet.co.nz-20140418154724-jxl715g8d68wxyf4 parent: rousskov@measurement-factory.com-20140416215808-1298nx9q10dcy6ag committer: Amos Jeffries branch nick: trunk timestamp: Fri 2014-04-18 08:47:24 -0700 message: squidclient: Support TLS for testing https:// URLs and HTTPS servers Also adds detection of the GnuTLS library which is used to provide this squidclient feature. ------------------------------------------------------------ # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: squid3@treenet.co.nz-20140418154724-jxl715g8d68wxyf4 # target_branch: http://bzr.squid-cache.org/bzr/squid3/trunk/ # testament_sha1: 4114b295f755e35bcd8762ba903e18d6bff48b98 # timestamp: 2014-04-18 15:53:44 +0000 # source_branch: http://bzr.squid-cache.org/bzr/squid3/trunk/ # base_revision_id: rousskov@measurement-factory.com-20140416215808-\ # 1298nx9q10dcy6ag # # Begin patch === modified file 'configure.ac' --- configure.ac 2014-04-08 15:52:58 +0000 +++ configure.ac 2014-04-18 15:47:24 +0000 @@ -1213,6 +1213,53 @@ AC_CHECK_LIB(md5, MD5Init, [CRYPTLIB="$CRYPTLIB -lmd5"]) AC_SUBST(CRYPTLIB) +SSLLIB="" + +dnl User may want to disable GnuTLS +AC_ARG_WITH(gnutls, + AS_HELP_STRING([--without-gnutls], + [Do not use GnuTLS for SSL. Default: auto-detect]), [ +case "$with_gnutls" in + yes|no) + : # Nothing special to do here + ;; + *) + if test ! -d "$withval" ; then + AC_MSG_ERROR([--with-gnutls path does not point to a directory]) + fi + LIBGNUTLS_PATH="-L$with_gnutls/lib" + CPPFLAGS="-I$with_gnutls/include $CPPFLAGS" + esac +]) +AH_TEMPLATE(USE_GNUTLS,[GnuTLS support is available]) +if test "x$with_gnutls" != "xno"; then + AC_CHECK_HEADERS(gnutls/gnutls.h gnutls/x509.h) + + # User may have provided a custom location for GnuTLS. Otherwise... + SQUID_STATE_SAVE(squid_gnutls_state) + LIBS="$LIBS $LIBGNUTLS_PATH" + + # auto-detect using pkg-config + PKG_CHECK_MODULES([LIBGNUTLS],[gnutls],,[ + ## find the package without pkg-config + AC_CHECK_LIB(gnutls,gnutls_init,[LIBGNUTLS_LIBS="-lgnutls"]) + ]) + + SQUID_STATE_ROLLBACK(squid_gnutls_state) #de-pollute LIBS + + if test "x$with_gnutls" = "xyes" -a "x$LIBGNUTLS_LIBS" = "x"; then + AC_MSG_ERROR([Required GnuTLS library not found]) + fi + if test "x$LIBGNUTLS_LIBS" != "x" ; then + CXXFLAGS="$LIBGNUTLS_CFLAGS $CXXFLAGS" + SSLLIB="$LIBGNUTLS_PATH $LIBGNUTLS_LIBS $SSLLIB" + AC_DEFINE(USE_GNUTLS,1,[GnuTLS support is available]) + else + with_gnutls=no + fi +fi +AC_MSG_NOTICE([GnuTLS library support: ${with_gnutls:=auto} ${LIBGNUTLS_PATH} ${LIBGNUTLS_LIBS}]) + dnl User may specify OpenSSL is needed from a non-standard location AC_ARG_WITH(openssl, AS_HELP_STRING([--with-openssl=PATH], === modified file 'tools/squidclient/Makefile.am' --- tools/squidclient/Makefile.am 2014-03-30 01:47:38 +0000 +++ tools/squidclient/Makefile.am 2014-04-18 15:47:24 +0000 @@ -12,6 +12,7 @@ $(top_builddir)/lib/libmiscencoding.la \ $(top_builddir)/lib/libmiscutil.la \ $(COMPAT_LIB) \ + $(LIBGNUTLS_LIBS) \ $(NETTLELIB) \ $(KRB5LIBS) \ $(XTRA_LIBS) @@ -49,4 +50,6 @@ squidclient.cc \ stub_debug.cc \ test_tools.cc \ - time.cc + time.cc \ + Transport.cc \ + Transport.h === modified file 'tools/squidclient/Parameters.h' --- tools/squidclient/Parameters.h 2014-02-20 13:03:07 +0000 +++ tools/squidclient/Parameters.h 2014-04-18 15:47:24 +0000 @@ -19,6 +19,10 @@ int verbosityLevel; }; +/// display debug messages at varying verbosity levels +#define debugVerbose(LEVEL, MESSAGE) \ + while ((LEVEL) <= scParams.verbosityLevel) {std::cerr << MESSAGE << std::endl; break;} + /// global squidcleint parameters extern Parameters scParams; === added file 'tools/squidclient/Transport.cc' --- tools/squidclient/Transport.cc 1970-01-01 00:00:00 +0000 +++ tools/squidclient/Transport.cc 2014-04-18 15:47:24 +0000 @@ -0,0 +1,499 @@ +#include "squid.h" +#include "ip/Address.h" +#include "tools/squidclient/Ping.h" +#include "tools/squidclient/Transport.h" + +#if HAVE_GETOPT_H +#include +#endif +#if HAVE_GNUTLS_X509_H +#include +#endif + +Transport::TheConfig Transport::Config; + +/// the current server connection FD +int conn = -1; + +void +Transport::TheConfig::usage() +{ + std::cerr << "Connection Settings" << std::endl + << " -h | --host host Send message to server on 'host'. Default is localhost." << std::endl + << " -l | --local host Specify a local IP address to bind to. Default is none." << std::endl + << " -p | --port port Port number on server to contact. Default is " << CACHE_HTTP_PORT << "." << std::endl + << " -T timeout Timeout in seconds for read/write operations" << std::endl +#if USE_GNUTLS + << " --tls [TLS options] Use TLS on the connection" << std::endl + << std::endl + << " TLS options:" << std::endl + << " --anonymous Use Anonymous TLS. Sets default parameters:" << std::endl + << " \"PERFORMANCE:+ANON-ECDH:+ANON-DH\"" << std::endl + << " --params=\"...\" Use the given parameters." << std::endl + << " --cert=FILE Path to a PEM file holding the client X.509 certificate chain." << std::endl + << " May be repeated if there are multiple certificates to use for the server." << std::endl + << " --trusted-ca=PATH Path to a PEM file holding trusted CA certificate(s)." << std::endl + << " May be repeated." << std::endl + << " Example path: \"/etc/ssl/certs/ca-certificates.crt\"" << std::endl + << std::endl; +#endif +} + +bool +Transport::TheConfig::parseCommandOpts(int argc, char *argv[], int c, int &optIndex) +{ + bool tls = false; + const char *shortOpStr = "A:C:h:l:p:P:T:?"; + + // options for controlling squidclient transport connection + static struct option longOptions[] = { + {"anonymous", no_argument, 0, '\1'}, + {"tls", no_argument, 0, '\3'}, + {"trusted-ca", required_argument, 0, 'A'}, + {"cert", required_argument, 0, 'C'}, + {"host", required_argument, 0, 'h'}, + {"local", required_argument, 0, 'l'}, + {"port", required_argument, 0, 'p'}, + {"params", required_argument, 0, 'P'}, + {0, 0, 0, 0} + }; + + int saved_opterr = opterr; + opterr = 0; // suppress errors from getopt + do { + switch (c) { + case '\1': + tls = true; + tlsAnonymous = true; + params = "PERFORMANCE:+ANON-ECDH:+ANON-DH"; + break; + + case '\3': + tls = true; + break; + + case 'A': + tls = true; + caFiles.push_back(std::string(optarg)); + break; + + case 'C': + tls = true; + certFiles.push_back(std::string(optarg)); + break; + + case 'h': + hostname = optarg; + break; + + case 'l': + localHost = optarg; + break; + + case 'p': /* port number */ + sscanf(optarg, "%hd", &port); + if (port < 1) + port = CACHE_HTTP_PORT; /* default */ + break; + + case 'P': + tls = true; + params = optarg; + break; + + case 'T': + ioTimeout = atoi(optarg); + break; + + default: + if (tls) + Transport::InitTls(); + + // rewind and let the caller handle unknown options + --optind; + opterr = saved_opterr; + return true; + } + } while ((c = getopt_long(argc, argv, shortOpStr, longOptions, &optIndex)) != -1); + + if (tls) + Transport::InitTls(); + + opterr = saved_opterr; + return false; +} + +/// Set up the source socket address from which to send. +static int +client_comm_bind(int sock, const Ip::Address &addr) +{ + static struct addrinfo *AI = NULL; + addr.getAddrInfo(AI); + int res = bind(sock, AI->ai_addr, AI->ai_addrlen); + Ip::Address::FreeAddrInfo(AI); + return res; +} + +static void +resolveDestination(Ip::Address &iaddr) +{ + struct addrinfo *AI = NULL; + + if (Transport::Config.localHost) { + debugVerbose(2, "Resolving " << Transport::Config.localHost << " ..."); + + if ( !iaddr.GetHostByName(Transport::Config.localHost) ) { + std::cerr << "ERROR: Cannot resolve " << Transport::Config.localHost << ": Host unknown." << std::endl; + exit(1); + } + } else { + debugVerbose(2, "Resolving " << Transport::Config.hostname << " ..."); + /* Process the remote host name to locate the Protocol required + in case we are being asked to link to another version of squid */ + if ( !iaddr.GetHostByName(Transport::Config.hostname) ) { + std::cerr << "ERROR: Cannot resolve " << Transport::Config.hostname << ": Host unknown." << std::endl; + exit(1); + } + } + + iaddr.getAddrInfo(AI); + if ((conn = socket(AI->ai_family, AI->ai_socktype, 0)) < 0) { + std::cerr << "ERROR: could not open socket to " << iaddr << std::endl; + Ip::Address::FreeAddrInfo(AI); + exit(1); + } + Ip::Address::FreeAddrInfo(AI); + + if (Transport::Config.localHost) { + if (client_comm_bind(conn, iaddr) < 0) { + std::cerr << "ERROR: could not bind socket to " << iaddr << std::endl; + exit(1); + } + + iaddr.setEmpty(); + + debugVerbose(2, "Resolving... " << Transport::Config.hostname); + + if ( !iaddr.GetHostByName(Transport::Config.hostname) ) { + std::cerr << "ERROR: Cannot resolve " << Transport::Config.hostname << ": Host unknown." << std::endl; + exit(1); + } + } + + iaddr.port(Transport::Config.port); +} + +/// Set up the destination socket address for message to send to. +static int +client_comm_connect(int sock, const Ip::Address &addr) +{ + static struct addrinfo *AI = NULL; + addr.getAddrInfo(AI); + int res = connect(sock, AI->ai_addr, AI->ai_addrlen); + Ip::Address::FreeAddrInfo(AI); + Ping::TimerStart(); + return res; +} + +bool +Transport::Connect() +{ + Ip::Address iaddr; + resolveDestination(iaddr); + + debugVerbose(2, "Connecting... " << Config.hostname << " (" << iaddr << ")"); + + if (client_comm_connect(conn, iaddr) < 0) { + char hostnameBuf[MAX_IPSTRLEN]; + iaddr.toUrl(hostnameBuf, MAX_IPSTRLEN); + std::cerr << "ERROR: Cannot connect to " << hostnameBuf + << (!errno ?": Host unknown." : "") << std::endl; + exit(1); + } + debugVerbose(2, "Connected to: " << Config.hostname << " (" << iaddr << ")"); + + // do any TLS setup that might be needed + if (!Transport::MaybeStartTls(Config.hostname)) + return false; + + return true; +} + +ssize_t +Transport::Write(void *buf, size_t len) +{ + if (conn < 0) + return -1; + + if (Config.tlsEnabled) { +#if USE_GNUTLS + gnutls_record_send(Config.session, buf, len); + return len; +#else + return 0; +#endif + } else { + +#if _SQUID_WINDOWS_ + return send(conn, buf, len, 0); +#else + alarm(Config.ioTimeout); + return write(conn, buf, len); +#endif + } +} + +ssize_t +Transport::Read(void *buf, size_t len) +{ + if (conn < 0) + return -1; + + if (Config.tlsEnabled) { +#if USE_GNUTLS + return gnutls_record_recv(Config.session, buf, len); +#else + return 0; +#endif + } else { + +#if _SQUID_WINDOWS_ + return recv(conn, buf, len, 0); +#else + alarm(Config.ioTimeout); + return read(conn, buf, len); +#endif + } +} + +void +Transport::CloseConnection() +{ + (void) close(conn); + conn = -1; +} + +#if USE_GNUTLS +/* This function will verify the peer's certificate, and check + * if the hostname matches, as well as the activation, expiration dates. + */ +static int +verifyByCA(gnutls_session_t session) +{ + /* read hostname */ + const char *hostname = static_cast(gnutls_session_get_ptr(session)); + + /* This verification function uses the trusted CAs in the credentials + * structure. So you must have installed one or more CA certificates. + */ + unsigned int status; + if (gnutls_certificate_verify_peers3(session, hostname, &status) < 0) { + std::cerr << "VERIFY peers failure"; + return GNUTLS_E_CERTIFICATE_ERROR; + } + + gnutls_certificate_type_t type = gnutls_certificate_type_get(session); + gnutls_datum_t out; + if (gnutls_certificate_verification_status_print(status, type, &out, 0) < 0) { + std::cerr << "VERIFY status failure"; + return GNUTLS_E_CERTIFICATE_ERROR; + } + + std::cerr << "VERIFY DATUM: " << out.data << std::endl; + gnutls_free(out.data); + + if (status != 0) /* Certificate is not trusted */ + return GNUTLS_E_CERTIFICATE_ERROR; + + /* notify gnutls to continue handshake normally */ + return GNUTLS_E_SUCCESS; +} + +static int +verifyTlsCertificate(gnutls_session_t session) +{ + // XXX: 1) try to verify using DANE -> Secure Authenticated Connection + + // 2) try to verify using CA + if (verifyByCA(session) == GNUTLS_E_SUCCESS) { + std::cerr << "SUCCESS: CA verified Encrypted Connection" << std::endl; + return GNUTLS_E_SUCCESS; + } + + // 3) fails both is insecure, but show the results anyway. + std::cerr << "WARNING: Insecure Connection" << std::endl; + return GNUTLS_E_SUCCESS; +} +#endif + +void +Transport::InitTls() +{ +#if USE_GNUTLS + debugVerbose(3, "Initializing TLS library..."); + // NP: gnutls init is re-entrant and lock-counted with deinit but not thread safe. + if (gnutls_global_init() != GNUTLS_E_SUCCESS) { + std::cerr << "FATAL ERROR: TLS Initialize failed: " << xstrerror() << std::endl; + exit(1); + } + + Config.tlsEnabled = true; + + // Initialize for anonymous TLS + gnutls_anon_allocate_client_credentials(&Config.anonCredentials); + + // Initialize for X.509 certificate exchange + gnutls_certificate_allocate_credentials(&Config.certCredentials); + for (std::list::const_iterator i = Config.caFiles.begin(); i != Config.caFiles.end(); ++i) { + int x = gnutls_certificate_set_x509_trust_file(Config.certCredentials, (*i).c_str(), GNUTLS_X509_FMT_PEM); + if (x < 0) { + debugVerbose(3, "WARNING: Failed to load Certificate Authorities from " << *i); + } else { + debugVerbose(3, "Loaded " << x << " Certificate Authorities from " << *i); + } + } + gnutls_certificate_set_verify_function(Config.certCredentials, verifyTlsCertificate); + + for (std::list::const_iterator i = Config.certFiles.begin(); i != Config.certFiles.end(); ++i) { + if (gnutls_certificate_set_x509_key_file(Transport::Config.certCredentials, (*i).c_str(), (*i).c_str(), GNUTLS_X509_FMT_PEM) != GNUTLS_E_SUCCESS) { + debugVerbose(3, "WARNING: Failed to load Certificate from " << *i); + } else { + debugVerbose(3, "Loaded Certificate from " << *i); + } + } + +#else + std::cerr << "ERROR: TLS support not available." << std::endl; +#endif +} + +#if USE_GNUTLS + +// perform the actual handshake exchange with remote server +static bool +doTlsHandshake(const char *type) +{ + // setup the connection for TLS + gnutls_transport_set_int(Transport::Config.session, conn); + gnutls_handshake_set_timeout(Transport::Config.session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); + + debugVerbose(2, type << " TLS handshake ... "); + + int ret = 0; + do { + ret = gnutls_handshake(Transport::Config.session); + } while (ret < 0 && gnutls_error_is_fatal(ret) == 0); + + if (ret < 0) { + std::cerr << "ERROR: " << type << " TLS Handshake failed (" << ret << ") " + << gnutls_alert_get_name(gnutls_alert_get(Transport::Config.session)) + << std::endl; + gnutls_perror(ret); + gnutls_deinit(Transport::Config.session); + return false; + } + + char *desc = gnutls_session_get_desc(Transport::Config.session); + debugVerbose(3, "TLS Session info: " << std::endl << desc << std::endl); + gnutls_free(desc); + return true; +} + +static bool +loadTlsParameters() +{ + const char *err = NULL; + int x; + if ((x = gnutls_priority_set_direct(Transport::Config.session, Transport::Config.params, &err)) != GNUTLS_E_SUCCESS) { + if (x == GNUTLS_E_INVALID_REQUEST) + std::cerr << "ERROR: Syntax error at: " << err << std::endl; + gnutls_perror(x); + return false; + } + return true; +} + +// attempt an anonymous TLS handshake +// this encrypts the connection but does not secure it +// so many public servers do not support this handshake type. +static bool +tryTlsAnonymous() +{ + if (!loadTlsParameters()) + return false; + + // put the anonymous credentials to the current session + int x; + if ((x = gnutls_credentials_set(Transport::Config.session, GNUTLS_CRD_ANON, Transport::Config.anonCredentials)) != GNUTLS_E_SUCCESS) { + std::cerr << "ERROR: Anonymous TLS credentials setup failed (" << x << ") " << std::endl; + gnutls_perror(x); + return false; + } + + return doTlsHandshake("Anonymous"); +} + +// attempt a X.509 certificate exchange +// this both encrypts and authenticates the connection +static bool +tryTlsCertificate(const char *hostname) +{ + gnutls_session_set_ptr(Transport::Config.session, (void *) hostname); + gnutls_server_name_set(Transport::Config.session, GNUTLS_NAME_DNS, hostname, strlen(hostname)); + + if (!loadTlsParameters()) + return false; + + // put the X.509 credentials to the current session + gnutls_credentials_set(Transport::Config.session, GNUTLS_CRD_CERTIFICATE, Transport::Config.certCredentials); + + // setup the connection for TLS + gnutls_transport_set_int(Transport::Config.session, conn); + gnutls_handshake_set_timeout(Transport::Config.session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); + + return doTlsHandshake("X.509"); +} +#endif + +bool +Transport::MaybeStartTls(const char *hostname) +{ +#if USE_GNUTLS + if (Config.tlsEnabled) { + + // Initialize TLS session + gnutls_init(&Transport::Config.session, GNUTLS_CLIENT); + + if (Transport::Config.tlsAnonymous && !tryTlsAnonymous()) { + gnutls_deinit(Config.session); + return false; + } + + if (!tryTlsCertificate(hostname)) { + gnutls_deinit(Config.session); + return false; + } + } +#endif + return true; +} + +void +Transport::ShutdownTls() +{ +#if USE_GNUTLS + if (!Config.tlsEnabled) + return; + + debugVerbose(3, "Shutting down TLS library..."); + + // release any existing session and credentials + gnutls_deinit(Config.session); + gnutls_anon_free_client_credentials(Config.anonCredentials); + gnutls_certificate_free_credentials(Config.certCredentials); + + // NP: gnutls init is re-entrant and lock-counted with deinit but not thread safe. + gnutls_global_deinit(); + Config.tlsEnabled = false; +#endif +} === added file 'tools/squidclient/Transport.h' --- tools/squidclient/Transport.h 1970-01-01 00:00:00 +0000 +++ tools/squidclient/Transport.h 2014-04-18 15:47:24 +0000 @@ -0,0 +1,113 @@ +#ifndef SQUID_TOOLS_SQUIDCLIENT_TRANSPORT_H +#define SQUID_TOOLS_SQUIDCLIENT_TRANSPORT_H + +#include "tools/squidclient/Parameters.h" + +#if HAVE_GNUTLS_GNUTLS_H +#include +#endif +#include +#include + +namespace Transport +{ + +/// parameters controlling outgoing connection +class TheConfig +{ +public: + TheConfig() : + ioTimeout(120), + localHost(NULL), + port(CACHE_HTTP_PORT), + tlsEnabled(false), + tlsAnonymous(false) + { + params = "NORMAL"; + hostname = "localhost"; + } + +// TODO: implicit transport options depending on the protocol-specific options +// ie --https enables TLS connection settings + + /// display Transport Options command line help to stderr + void usage(); + + /** + * parse transport related command line options + * \return true if there are other options still to parse + */ + bool parseCommandOpts(int argc, char *argv[], int c, int &optIndex); + + /// I/O operation timeout + int ioTimeout; + + /// the local hostname to bind as for outgoing IP + const char *localHost; + + /// the destination server host name to contact + const char *hostname; + + /// port on the server to contact + uint16_t port; + + /// whether to enable TLS on the server connnection + bool tlsEnabled; + + /// whether to do anonymous TLS (non-authenticated) + bool tlsAnonymous; + + /// The TLS parameters (list of ciphers, versions, flags) + /// Default is "NORMAL" unless tlsAnonymous is used, + /// in which case it becomes "PERFORMANCE:+ANON-ECDH:+ANON-DH". + /// see http://gnutls.org/manual/html_node/Priority-Strings.html + const char *params; + + // client certificate PEM file(s) + std::list certFiles; + + // client trusted x509 certificate authorities file + std::list caFiles; + +#if USE_GNUTLS + /// anonymous client credentials + gnutls_anon_client_credentials_t anonCredentials; + + // client x509 certificate credentials + gnutls_certificate_credentials_t certCredentials; + + /// TLS session state + gnutls_session_t session; +#endif +}; + +extern TheConfig Config; + +/// locate and connect to the configured server +bool Connect(); + +/// close the current connection +void CloseConnection(); + +/// Initialize TLS library environment when necessary. +void InitTls(); + +/// perform TLS handshake on the currently open connection if +/// TLS library has been initialized. +/// return false on errors, true otherwise even if TLS not performed. +bool MaybeStartTls(const char *hostname); + +/// De-initialize TLS library environment when necessary. +void ShutdownTls(); + +/// write len bytes to the currently open connection. +/// \return the number of bytes written, or -1 on errors +ssize_t Write(void *buf, size_t len); + +/// read up to len bytes from the currently open connection. +/// \return the number of bytes read, or -1 on errors +ssize_t Read(void *buf, size_t len); + +} // namespace Transport + +#endif /* SQUID_TOOLS_SQUIDCLIENT_TRANSPORT_H */ === modified file 'tools/squidclient/squidclient.1' --- tools/squidclient/squidclient.1 2014-02-20 13:03:07 +0000 +++ tools/squidclient/squidclient.1 2014-04-18 15:47:24 +0000 @@ -7,10 +7,11 @@ . .SH SYNOPSIS .if !'po4a'hide' .B squidclient -.if !'po4a'hide' .B "[ \-\-ping [ping-options] ] " -.if !'po4a'hide' .B "[ \-aknNrsv ] [ \-A" +.if !'po4a'hide' .B "[ \-aknNrsv ] " +.if !'po4a'hide' .B "[ \-\-ping [ping\-options] ] " +.if !'po4a'hide' .B "[ \-\-tls [tls\-options] ] [ \-A" string -.if !'po4a'hide' .B "] [ \-h" +.if !'po4a'hide' .B "] [ \-h | \-\-host" remote host .if !'po4a'hide' .B "] [ \-H '" string @@ -18,11 +19,11 @@ IMS .if !'po4a'hide' .B "] [ \-j '" Host header -.if !'po4a'hide' .B "' ] [ \-l" -local host +.if !'po4a'hide' .B "' ] [ \-l | \-\-local" +host .if !'po4a'hide' .B "] [ \-m" method -.if !'po4a'hide' .B "] [ \-p" +.if !'po4a'hide' .B "] [ \-p | \-\-port" port .if !'po4a'hide' .B "] [ \-P" file @@ -49,6 +50,14 @@ interval .if !'po4a'hide' .B "] " . +.if !'po4a'hide' .B "TLS options: [ \-\-anonymous ] [ \-\-trusted\-ca" +CA certificates file +.if !'po4a'hide' .B "...] [ \-\-cert" +client X.509 certificate file +.if !'po4a'hide' .B "] [ \-\-params" +TLS session parameters +.if !'po4a'hide' .B "] " +. .SH DESCRIPTION .B squidclient is a tool providing a command line interface for retrieving URLs. @@ -70,8 +79,8 @@ as User-Agent: header. To omit the header completely set string to empty (''). . .if !'po4a'hide' .TP -.if !'po4a'hide' .B "\-h host" -Retrieve URL from cache on hostname. Default is +.if !'po4a'hide' .B "\-h | \-\-host host" +Retrieve URL from server host. Default is .B localhost . .if !'po4a'hide' .TP @@ -93,7 +102,7 @@ Keep the connection active. Default is to do only one request then close. . .if !'po4a'hide' .TP -.if !'po4a'hide' .B "\-l host" +.if !'po4a'hide' .B "\-l | \-\-local host" Specify a local IP address to bind to. Default is none. . .if !'po4a'hide' .TP @@ -192,6 +201,31 @@ .if !'po4a'hide' .B "\-I interval" Ping interval in seconds (default 1 second). . +.if !'po4a'hide' .TP 10 +.if !'po4a'hide' .B "\-\-tls [options]" +Use Transport Layer Security on the connection. +. +.if !'po4a'hide' .TP 12 +.if !'po4a'hide' .B "\-\-anonymous" +Use TLS with unauthenticated (anonymous) certificate. +. +.if !'po4a'hide' .TP +.if !'po4a'hide' .B "\-\-cert file" +File containing client X.509 certificate in PEM format. +May be repeated to load several client certificates. +. +.if !'po4a'hide' .TP 12 +.if !'po4a'hide' .B "\-\-trusted-ca file" +File containing trusted Certificate Authority (CA) certificates in PEM format. +May be repeated to load any number of files. +. +.if !'po4a'hide' .TP 12 +.if !'po4a'hide' .B "\-\-params values" +TLS library specific parameters for the communication session. +See the library documentation for details on valid parameters. +.if !'po4a'hide' .I "GnuTLS: http://gnutls.org/manual/html_node/Priority\-Strings.html" +If repeated only the last value will have effect. +. .SH AUTHOR Derived from Harvest. Further developed by by numerous individuals from the internet community. Development is led by Duane Wessels of the === modified file 'tools/squidclient/squidclient.cc' --- tools/squidclient/squidclient.cc 2014-03-30 01:47:38 +0000 +++ tools/squidclient/squidclient.cc 2014-04-18 15:47:24 +0000 @@ -38,6 +38,7 @@ #include "tools/squidclient/gssapi_support.h" #include "tools/squidclient/Parameters.h" #include "tools/squidclient/Ping.h" +#include "tools/squidclient/Transport.h" #if _SQUID_WINDOWS_ /** \cond AUTODOCS-IGNORE */ @@ -84,20 +85,11 @@ #define HEADERLEN 65536 #endif -/// display debug messages at varying verbosity levels -#define debugVerbose(LEVEL, MESSAGE) \ - while ((LEVEL) <= scParams.verbosityLevel) {std::cerr << MESSAGE << std::endl; break;} - /* Local functions */ -static int client_comm_bind(int, const Ip::Address &); - -static int client_comm_connect(int, const Ip::Address &); static void usage(const char *progname); void pipe_handler(int sig); static void set_our_signal(void); -static ssize_t myread(int fd, void *buf, size_t len); -static ssize_t mywrite(int fd, void *buf, size_t len); Parameters scParams; @@ -106,7 +98,6 @@ static struct stat sb; int total_bytes = 0; -int io_timeout = 120; #if _SQUID_AIX_ /* Bug 3854: AIX 6.1 tries to link in this fde.h global symbol @@ -129,19 +120,16 @@ { std::cerr << "Version: " << VERSION << std::endl << "Usage: " << progname << " [Basic Options] [HTTP Options]" << std::endl - << std::endl - << "Basic Options:" << std::endl - << " -h host Send message to server on 'host'. Default is localhost." << std::endl - << " -l host Specify a local IP address to bind to. Default is none." << std::endl - << " -p port Port number on server to contact. Default is " << CACHE_HTTP_PORT << "." << std::endl + << std::endl; + std::cerr << " -s | --quiet Silent. Do not print response message to stdout." << std::endl - << " -T timeout Timeout value (seconds) for read/write operations" << std::endl << " -v | --verbose Verbose debugging. Repeat (-vv) to increase output level." << std::endl << " Levels:" << std::endl << " 1 - Print outgoing request message to stderr." << std::endl << " 2 - Print action trace to stderr." << std::endl << " --help Display this help text." << std::endl << std::endl; + Transport::Config.usage(); Ping::Config.usage(); std::cerr << "HTTP Options:" << std::endl @@ -171,16 +159,13 @@ int main(int argc, char *argv[]) { - int conn, len, bytesWritten; - uint16_t port; + int len, bytesWritten; bool to_stdout, reload; int keep_alive = 0; int opt_noaccept = 0; #if HAVE_GSSAPI int www_neg = 0, proxy_neg = 0; #endif - const char *hostname, *localhost; - Ip::Address iaddr; char url[BUFSIZ], msg[MESSAGELEN], buf[BUFSIZ]; char extra_hdrs[HEADERLEN]; const char *method = "GET"; @@ -197,10 +182,7 @@ const char *useragent = NULL; /* set the defaults */ - hostname = "localhost"; - localhost = NULL; extra_hdrs[0] = '\0'; - port = CACHE_HTTP_PORT; to_stdout = true; reload = false; @@ -212,7 +194,7 @@ url[BUFSIZ - 1] = '\0'; int optIndex = 0; - const char *shortOpStr = "aA:h:j:V:l:P:i:kmnN:p:rsvt:p:H:T:u:U:w:W:?"; + const char *shortOpStr = "aA:h:j:V:l:P:i:kmnN:p:rsvt:H:T:u:U:w:W:?"; // options for controlling squidclient static struct option basicOptions[] = { @@ -220,7 +202,11 @@ {"help", no_argument, 0, '?'}, {"verbose", no_argument, 0, 'v'}, {"quiet", no_argument, 0, 's'}, + {"host", required_argument, 0, 'h'}, + {"local", required_argument, 0, 'l'}, + {"port", required_argument, 0, 'p'}, {"ping", no_argument, 0, '\1'}, + {"tls", no_argument, 0, '\3'}, {0, 0, 0, 0} }; @@ -231,9 +217,18 @@ switch (c) { case '\1': to_stdout = 0; - if (Ping::Config.parseCommandOpts(argc, argv, c, optIndex)) - continue; - break; + Ping::Config.parseCommandOpts(argc, argv, c, optIndex); + continue; + + case 'h': /* remote host */ + case 'l': /* local host */ + case 'p': /* port number */ + // rewind and let the Transport::Config parser handle + optind -= 2; + + case '\3': // request over a TLS connection + Transport::Config.parseCommandOpts(argc, argv, c, optIndex); + continue; default: // fall through to next switch break; @@ -252,10 +247,6 @@ useragent = optarg; break; - case 'h': /* remote host */ - hostname = optarg; - break; - case 'j': host = optarg; break; @@ -264,10 +255,6 @@ version = optarg; break; - case 'l': /* local host */ - localhost = optarg; - break; - case 's': /* silent */ to_stdout = false; break; @@ -280,12 +267,6 @@ reload = true; break; - case 'p': /* port number */ - sscanf(optarg, "%hd", &port); - if (port < 1) - port = CACHE_HTTP_PORT; /* default */ - break; - case 'P': put_file = xstrdup(optarg); break; @@ -313,7 +294,7 @@ break; case 'T': - io_timeout = atoi(optarg); + Transport::Config.ioTimeout = atoi(optarg); break; case 'u': @@ -380,9 +361,9 @@ } // embed the -w proxy password into old-style cachemgr URLs if (at) - snprintf(url, BUFSIZ, "cache_object://%s/%s@%s", hostname, t, at); + snprintf(url, BUFSIZ, "cache_object://%s/%s@%s", Transport::Config.hostname, t, at); else - snprintf(url, BUFSIZ, "cache_object://%s/%s", hostname, t); + snprintf(url, BUFSIZ, "cache_object://%s/%s", Transport::Config.hostname, t); xfree(t); } if (put_file) { @@ -499,8 +480,8 @@ std::cerr << "ERROR: server host missing" << std::endl; } if (proxy_neg) { - if (hostname) { - snprintf(buf, BUFSIZ, "Proxy-Authorization: Negotiate %s\r\n", GSSAPI_token(hostname)); + if (Transport::Config.hostname) { + snprintf(buf, BUFSIZ, "Proxy-Authorization: Negotiate %s\r\n", GSSAPI_token(Transport::Config.hostname)); strcat(msg, buf); } else std::cerr << "ERROR: proxy server host missing" << std::endl; @@ -525,61 +506,13 @@ for (uint32_t i = 0; loops == 0 || i < loops; ++i) { size_t fsize = 0; - struct addrinfo *AI = NULL; - - debugVerbose(2, "Resolving... " << hostname); - - /* Connect to the server */ - - if (localhost) { - if ( !iaddr.GetHostByName(localhost) ) { - std::cerr << "ERROR: Cannot resolve " << localhost << ": Host unknown." << std::endl; - exit(1); - } - } else { - /* Process the remote host name to locate the Protocol required - in case we are being asked to link to another version of squid */ - if ( !iaddr.GetHostByName(hostname) ) { - std::cerr << "ERROR: Cannot resolve " << hostname << ": Host unknown." << std::endl; - exit(1); - } - } - - iaddr.getAddrInfo(AI); - if ((conn = socket(AI->ai_family, AI->ai_socktype, 0)) < 0) { - std::cerr << "ERROR: could not open socket to " << iaddr << std::endl; - Ip::Address::FreeAddrInfo(AI); - exit(1); - } - Ip::Address::FreeAddrInfo(AI); - - if (localhost && client_comm_bind(conn, iaddr) < 0) { - std::cerr << "ERROR: could not bind socket to " << iaddr << std::endl; - exit(1); - } - - iaddr.setEmpty(); - if ( !iaddr.GetHostByName(hostname) ) { - std::cerr << "ERROR: Cannot resolve " << hostname << ": Host unknown." << std::endl; - exit(1); - } - - iaddr.port(port); - - debugVerbose(2, "Connecting... " << hostname << " (" << iaddr << ")"); - - if (client_comm_connect(conn, iaddr) < 0) { - char hostnameBuf[MAX_IPSTRLEN]; - iaddr.toUrl(hostnameBuf, MAX_IPSTRLEN); - std::cerr << "ERROR: Cannot connect to " << hostnameBuf - << (!errno ?": Host unknown." : "") << std::endl; - exit(1); - } - debugVerbose(2, "Connected to: " << hostname << " (" << iaddr << ")"); + + if (!Transport::Connect()) + continue; /* Send the HTTP request */ debugVerbose(2, "Sending HTTP request ... "); - bytesWritten = mywrite(conn, msg, strlen(msg)); + bytesWritten = Transport::Write(msg, strlen(msg)); if (bytesWritten < 0) { std::cerr << "ERROR: write" << std::endl; @@ -596,7 +529,7 @@ lseek(put_fd, 0, SEEK_SET); while ((x = read(put_fd, buf, sizeof(buf))) > 0) { - x = mywrite(conn, buf, x); + x = Transport::Write(buf, x); total_bytes += x; @@ -615,18 +548,30 @@ setmode(1, O_BINARY); #endif - while ((len = myread(conn, buf, sizeof(buf))) > 0) { + while ((len = Transport::Read(buf, sizeof(buf))) > 0) { fsize += len; if (to_stdout && fwrite(buf, len, 1, stdout) != 1) std::cerr << "ERROR: writing to stdout: " << xstrerror() << std::endl; } +#if USE_GNUTLS + if (Transport::Config.tlsEnabled) { + if (len == 0) { + std::cerr << "- Peer has closed the TLS connection" << std::endl; + } else if (!gnutls_error_is_fatal(len)) { + std::cerr << "WARNING: " << gnutls_strerror(len) << std::endl; + } else { + std::cerr << "ERROR: " << gnutls_strerror(len) << std::endl; + } + } +#endif + #if _SQUID_WINDOWS_ setmode(1, O_TEXT); #endif - (void) close(conn); /* done with socket */ + Transport::CloseConnection(); if (Ping::LoopDone(i)) break; @@ -635,32 +580,10 @@ } Ping::DisplayStats(); + Transport::ShutdownTls(); return 0; } -/// Set up the source socket address from which to send. -static int -client_comm_bind(int sock, const Ip::Address &addr) -{ - static struct addrinfo *AI = NULL; - addr.getAddrInfo(AI); - int res = bind(sock, AI->ai_addr, AI->ai_addrlen); - Ip::Address::FreeAddrInfo(AI); - return res; -} - -/// Set up the destination socket address for message to send to. -static int -client_comm_connect(int sock, const Ip::Address &addr) -{ - static struct addrinfo *AI = NULL; - addr.getAddrInfo(AI); - int res = connect(sock, AI->ai_addr, AI->ai_addrlen); - Ip::Address::FreeAddrInfo(AI); - Ping::TimerStart(); - return res; -} - void pipe_handler(int sig) { @@ -684,25 +607,3 @@ signal(SIGPIPE, pipe_handler); #endif } - -static ssize_t -myread(int fd, void *buf, size_t len) -{ -#if _SQUID_WINDOWS_ - return recv(fd, buf, len, 0); -#else - alarm(io_timeout); - return read(fd, buf, len); -#endif -} - -static ssize_t -mywrite(int fd, void *buf, size_t len) -{ -#if _SQUID_WINDOWS_ - return send(fd, buf, len, 0); -#else - alarm(io_timeout); - return write(fd, buf, len); -#endif -}