=== modified file 'configure.ac' --- configure.ac 2014-04-08 15:52:58 +0000 +++ configure.ac 2014-04-12 02:03:43 +0000 @@ -1196,40 +1196,87 @@ esac ]) if test "x$with_nettle" != "xno" ; then AC_CHECK_LIB(nettle, nettle_md5_init,[ NETTLELIB="$NETTLELIBDIR -lnettle" AC_CHECK_HEADERS(nettle/md5.h) ],[with_nettle=no]) fi AC_MSG_NOTICE([Using Nettle cryptographic library: ${with_nettle:=yes}]) AC_SUBST(NETTLELIB) dnl Check for libcrypt CRYPTLIB= dnl Some of our helpers use crypt(3) which may be in libc, or in dnl libcrypt (eg FreeBSD) AC_CHECK_LIB(crypt, crypt, [CRYPTLIB="-lcrypt"]) dnl Solaris10 provides MD5 natively through libmd5 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], [Compile with the OpenSSL libraries. The path to the OpenSSL development libraries and headers installation can be specified if outside of the system standard directories]), [ case "$with_openssl" in yes|no) : # Nothing special to do here ;; *) if test ! -d "$withval" ; then AC_MSG_ERROR([--with-openssl path does not point to a directory]) fi LIBOPENSSL_PATH="-L$with_openssl/lib" CPPFLAGS="-I$with_openssl/include $CPPFLAGS" with_openssl=yes esac ]) === modified file 'tools/squidclient/Makefile.am' --- tools/squidclient/Makefile.am 2014-03-30 01:47:38 +0000 +++ tools/squidclient/Makefile.am 2014-03-31 09:45:08 +0000 @@ -1,52 +1,55 @@ include $(top_srcdir)/src/Common.am AUTOMAKE_OPTIONS = subdir-objects SUBDIRS = EXTRA_DIST = squidclient.1 man_MANS = squidclient.1 DISTCLEANFILES = LDADD = \ $(top_builddir)/src/ip/libip.la \ $(top_builddir)/lib/libmiscencoding.la \ $(top_builddir)/lib/libmiscutil.la \ $(COMPAT_LIB) \ + $(LIBGNUTLS_LIBS) \ $(NETTLELIB) \ $(KRB5LIBS) \ $(XTRA_LIBS) include $(top_srcdir)/doc/manuals/Substitute.am ## Several files need to be shared but we cannot depend on the other ## directories to be built. test_tools.cc: $(top_srcdir)/test-suite/test_tools.cc cp $(top_srcdir)/test-suite/test_tools.cc . stub_debug.cc: $(top_srcdir)/src/tests/stub_debug.cc cp $(top_srcdir)/src/tests/stub_debug.cc . time.cc: $(top_srcdir)/src/time.cc cp $(top_srcdir)/src/time.cc . # stock tools for unit tests - library independent versions of dlink_list # etc. # globals.cc is needed by test_tools.cc. # Neither of these should be disted from here. TESTSOURCES= test_tools.cc CLEANFILES += test_tools.cc stub_debug.cc time.cc ## ##### squidclient ##### bin_PROGRAMS = squidclient squidclient_SOURCES = \ gssapi_support.cc \ gssapi_support.h \ Parameters.h \ Ping.cc \ Ping.h \ 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-03-08 03:36:18 +0000 @@ -2,24 +2,28 @@ #define _SQUID_TOOLS_SQUIDCLIENT_PARAMETERS_H /** * squidclient command line parameters. */ class Parameters { public: Parameters() : verbosityLevel(0) {} /** * What verbosity level to display. * * 0 : display no debug traces * 1 : display outgoing request message * 2+ : display all actions taken */ 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; #endif /* _SQUID_TOOLS_SQUIDCLIENT_PARAMETERS_H */ === added file 'tools/squidclient/Transport.cc' --- tools/squidclient/Transport.cc 1970-01-01 00:00:00 +0000 +++ tools/squidclient/Transport.cc 2014-04-16 09:29:04 +0000 @@ -0,0 +1,450 @@ +#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." << std::endl + << std::endl; +#endif +} + +bool +Transport::TheConfig::parseCommandOpts(int argc, char *argv[], int c, int &optIndex) +{ + const char *shortOpStr = "h:l:p:T:?"; + + // options for controlling squidclient ping mode + static struct option pingOptions[] = { + {"anonymous", no_argument, 0, '\1'}, + {"host", required_argument, 0, 'h'}, + {"local", required_argument, 0, 'l'}, + {"port", required_argument, 0, 'p'}, + {0, 0, 0, 0} + }; + + int saved_opterr = opterr; + opterr = 0; // suppress errors from getopt + while ((c = getopt_long(argc, argv, shortOpStr, pingOptions, &optIndex)) != -1) { + switch (c) { + case '\1': + tlsAnonymous = true; + 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 'T': + ioTimeout = atoi(optarg); + break; + + default: + Transport::InitTls(); + // rewind and let the caller handle unknown options + --optind; + opterr = saved_opterr; + return true; + } + } + + 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); + gnutls_certificate_set_x509_trust_file(Config.certCredentials, Config.caFile, GNUTLS_X509_FMT_PEM); + gnutls_certificate_set_verify_function(Config.certCredentials, verifyTlsCertificate); + +#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; +} + +// 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() +{ + /* Use default priorities */ + const char *ciphers = "PERFORMANCE:+ANON-ECDH:+ANON-DH"; + const char *err = NULL; + int x; + if ((x = gnutls_priority_set_direct(Transport::Config.session, ciphers, &err)) != GNUTLS_E_SUCCESS) { + if (x == GNUTLS_E_INVALID_REQUEST) + std::cerr << "ERROR: Syntax error at: " << err << std::endl; + gnutls_perror(x); + return false; + } + + // put the anonymous credentials to the current session + 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) +{ + /* If client holds a certificate it can be set using the following: + * + gnutls_certificate_set_x509_key_file(Transport::Config.certCredentials, "cert.pem", "key.pem", GNUTLS_X509_FMT_PEM); + */ + + gnutls_session_set_ptr(Transport::Config.session, (void *) hostname); + gnutls_server_name_set(Transport::Config.session, GNUTLS_NAME_DNS, hostname, strlen(hostname)); + + /* Use default priorities */ + const char *ciphers = "NORMAL"; + const char *err = NULL; + int x; + if ((x = gnutls_priority_set_direct(Transport::Config.session, ciphers, &err)) != GNUTLS_E_SUCCESS) { + if (x == GNUTLS_E_INVALID_REQUEST) + std::cerr << "ERROR: Syntax error at: " << err << std::endl; + gnutls_perror(x); + 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 + 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-12 14:55:09 +0000 @@ -0,0 +1,102 @@ +#ifndef SQUID_TOOLS_SQUIDCLIENT_TRANSPORT_H +#define SQUID_TOOLS_SQUIDCLIENT_TRANSPORT_H + +#include "tools/squidclient/Parameters.h" + +#if HAVE_GNUTLS_GNUTLS_H +#include +#endif + +namespace Transport +{ + +/// parameters controlling 'ping' mode message looping. +class TheConfig +{ +public: + TheConfig() : + ioTimeout(120), + localHost(NULL), + port(CACHE_HTTP_PORT), + tlsEnabled(false), + tlsAnonymous(false) + { + 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; + +#if USE_GNUTLS + + /// anonymous client credentials + gnutls_anon_client_credentials_t anonCredentials; + + // x509 certificate credentials + gnutls_certificate_credentials_t certCredentials; + + // x509 certificate authorities file + const char * caFile = "/etc/ssl/certs/ca-certificates.crt"; + + /// 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-16 12:53:38 +0000 @@ -1,116 +1,118 @@ .if !'po4a'hide' .TH squidclient 1 . .SH NAME .if !'po4a'hide' .B squidclient .if !'po4a'hide' \- A simple HTTP web client tool . .SH SYNOPSIS .if !'po4a'hide' .B squidclient -.if !'po4a'hide' .B "[ \-\-ping [ping-options] ] " -.if !'po4a'hide' .B "[ \-aknNrsv ] [ \-A" +.if !'po4a'hide' .B "[ \-\-ping [ping\-options] ] " +.if !'po4a'hide' .B "[ \-aknNrsv ] [ \-\-tls [tls\-options] ] [ \-A" string -.if !'po4a'hide' .B "] [ \-h" +.if !'po4a'hide' .B "] [ \-h | \-\-host" remote host .if !'po4a'hide' .B "] [ \-H '" string .if !'po4a'hide' .B "' ] [ \-i" 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 .if !'po4a'hide' .B "] [ \-t" count .if !'po4a'hide' .B "] [ \-T" timeout .if !'po4a'hide' .B "] [ \-u" user .if !'po4a'hide' .B "] [ \-U" user .if !'po4a'hide' .B "] [ \-V" version .if !'po4a'hide' .B "] [ \-w" password .if !'po4a'hide' .B "] [ \-W" password .if !'po4a'hide' .B "] " url . .if !'po4a'hide' .B "Ping options: [ \-g" count .if !'po4a'hide' .B "] [ \-I" interval .if !'po4a'hide' .B "] " . +.if !'po4a'hide' .B "TLS options: [ \-\-anonymous ]" +. .SH DESCRIPTION .B squidclient is a tool providing a command line interface for retrieving URLs. Designed for testing any HTTP 0.9, 1.0, or 1.1 web server or proxy. This tool can be combined with scripts to perform any basic HTTP operation. Some additional features for access to the .B squid proxy object cache and management information are provided. . .SH OPTIONS .if !'po4a'hide' .TP 12 .if !'po4a'hide' .B "\-a" Do NOT include Accept: header. . .if !'po4a'hide' .TP .if !'po4a'hide' .B "\-A 'string'" Send .B string 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 .if !'po4a'hide' .B "\-H 'string'" Extra headers to send. Use .B '\\n' for new lines. . .if !'po4a'hide' .TP .if !'po4a'hide' .B "\-i time" If\-Modified\-Since time (in Epoch seconds). . .if !'po4a'hide' .TP .if !'po4a'hide' .B "\-j hosthdr" Host header content . .if !'po4a'hide' .TP .if !'po4a'hide' .B "\-k" 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 .if !'po4a'hide' .B "\-m method" Request method, default is .I GET. Squid also supports a non-standard method called .I PURGE. You can use that to purge a specific URL from the cache. You need to have .I purge access setup in .B squid.conf similar to .I manager access. Here is an example: .if !'po4a'hide' .nf .if !'po4a'hide' acl purge method PURGE .if !'po4a'hide' http_access deny purge !localhost .if !'po4a'hide' .fi @@ -175,40 +177,48 @@ . .if !'po4a'hide' .TP .if !'po4a'hide' .B "\-W password" WWW authentication password . .if !'po4a'hide' .TP 10 .if !'po4a'hide' .B "\-\-ping [options]" Enable ping mode. Optional \-g and \-I parameters must follow immediately if used. Repeated use resets to default ping settings. . .if !'po4a'hide' .TP 12 .if !'po4a'hide' .B "\-g count" Ping mode, perform .I count iterations (default is to loop until interrupted). . .if !'po4a'hide' .TP .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. +. .SH AUTHOR Derived from Harvest. Further developed by by numerous individuals from the internet community. Development is led by Duane Wessels of the National Laboratory for Applied Network Research and funded by the National Science Foundation. . .SH COPYRIGHT Distributed under the GNU General Public License (GNU GPL) version 2 or later (GPLv2+). . .SH QUESTIONS Questions on the usage of this program can be sent to the .I Squid Users mailing list .if !'po4a'hide' . .SH REPORTING BUGS See http://wiki.squid-cache.org/SquidFaq/BugReporting for details of what you need to include with your bug report. .PP Report bugs or bug fixes using http://bugs.squid-cache.org/ .PP Report serious security bugs to === modified file 'tools/squidclient/squidclient.cc' --- tools/squidclient/squidclient.cc 2014-03-30 01:47:38 +0000 +++ tools/squidclient/squidclient.cc 2014-04-16 09:25:41 +0000 @@ -21,40 +21,41 @@ * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #include "squid.h" #include "base64.h" #include "ip/Address.h" #include "ip/tools.h" #include "rfc1123.h" #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 */ using namespace Squid; /** \endcond */ #endif #include #include #include #include #if _SQUID_WINDOWS_ #include #endif #if HAVE_SYS_SOCKET_H #include #endif #if HAVE_UNISTD_H #include #endif @@ -67,270 +68,250 @@ #if HAVE_FCNTL_H #include #endif #if HAVE_NETINET_IN_H #include #endif #if HAVE_GETOPT_H #include #endif #ifndef BUFSIZ #define BUFSIZ 8192 #endif #ifndef MESSAGELEN #define MESSAGELEN 65536 #endif #ifndef HEADERLEN #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; static int put_fd; static char *put_file = NULL; 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 * despite squidclient not using any of the fd_* code. */ fde *fde::Table = NULL; #endif #if _SQUID_WINDOWS_ void Win32SockCleanup(void) { WSACleanup(); return; } #endif static void usage(const char *progname) { 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 << " -a Do NOT include Accept: header." << std::endl << " -A User-Agent: header. Use \"\" to omit." << std::endl << " -H 'string' Extra headers to send. Use '\\n' for new lines." << std::endl << " -i IMS If-Modified-Since time (in Epoch seconds)." << std::endl << " -j hosthdr Host header content" << std::endl << " -k Keep the connection active. Default is to do only one request then close." << std::endl << " -m method Request method, default is GET." << std::endl #if HAVE_GSSAPI << " -n Proxy Negotiate(Kerberos) authentication" << std::endl << " -N WWW Negotiate(Kerberos) authentication" << std::endl #endif << " -P file Send content from the named file as request payload" << std::endl << " -r Force cache to reload URL" << std::endl << " -t count Trace count cache-hops" << std::endl << " -u user Proxy authentication username" << std::endl << " -U user WWW authentication username" << std::endl << " -V version HTTP Version. Use '-' for HTTP/0.9 omitted case" << std::endl << " -w password Proxy authentication password" << std::endl << " -W password WWW authentication password" << std::endl ; exit(1); } 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"; extern char *optarg; time_t ims = 0; int max_forwards = -1; const char *proxy_user = NULL; const char *proxy_password = NULL; const char *www_user = NULL; const char *www_password = NULL; const char *host = NULL; const char *version = "1.0"; const char *useragent = NULL; /* set the defaults */ - hostname = "localhost"; - localhost = NULL; extra_hdrs[0] = '\0'; - port = CACHE_HTTP_PORT; to_stdout = true; reload = false; Ip::ProbeTransport(); // determine IPv4 or IPv6 capabilities before parsing. if (argc < 2 || argv[argc-1][0] == '-') { usage(argv[0]); /* need URL */ } else if (argc >= 2) { strncpy(url, argv[argc - 1], BUFSIZ); 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[] = { /* These are the generic options for squidclient itself */ {"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} }; int c; while ((c = getopt_long(argc, argv, shortOpStr, basicOptions, &optIndex)) != -1) { // modules parse their own specific options 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; } switch (c) { case '\0': // dummy value for end-of-options break; case 'a': opt_noaccept = 1; break; case 'A': useragent = optarg; break; - case 'h': /* remote host */ - hostname = optarg; - break; - case 'j': host = optarg; break; case 'V': version = optarg; break; - case 'l': /* local host */ - localhost = optarg; - break; - case 's': /* silent */ to_stdout = false; break; case 'k': /* backward compat */ keep_alive = 1; break; case 'r': /* reload */ 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; case 'i': /* IMS */ ims = (time_t) atoi(optarg); break; case 'm': method = xstrdup(optarg); break; case 't': method = xstrdup("TRACE"); max_forwards = atoi(optarg); break; case 'H': if (strlen(optarg)) { char *t; strncpy(extra_hdrs, optarg, sizeof(extra_hdrs)); while ((t = strstr(extra_hdrs, "\\n"))) *t = '\r', *(t + 1) = '\n'; } break; case 'T': - io_timeout = atoi(optarg); + Transport::Config.ioTimeout = atoi(optarg); break; case 'u': proxy_user = optarg; break; case 'w': proxy_password = optarg; break; case 'U': www_user = optarg; break; case 'W': www_password = optarg; break; case 'n': #if HAVE_GSSAPI @@ -363,43 +344,43 @@ break; } } } #if _SQUID_WINDOWS_ { WSADATA wsaData; WSAStartup(2, &wsaData); atexit(Win32SockCleanup); } #endif /* Build the HTTP request */ if (strncmp(url, "mgr:", 4) == 0) { char *t = xstrdup(url + 4); const char *at = NULL; if (!strrchr(t, '@')) { // ignore any -w password if @ is explicit already. at = proxy_password; } // 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) { put_fd = open(put_file, O_RDONLY); set_our_signal(); if (put_fd < 0) { std::cerr << "ERROR: can't open file (" << xstrerror() << ")" << std::endl; exit(-1); } #if _SQUID_WINDOWS_ setmode(put_fd, O_BINARY); #endif if (fstat(put_fd, &sb) < 0) { std::cerr << "ERROR: can't identify length of file (" << xstrerror() << ")" << std::endl; } } if (!host) { @@ -482,227 +463,147 @@ if (!password) password = getpass("WWW password: "); #endif if (!password) { std::cerr << "ERROR: WWW password missing" << std::endl; exit(1); } snprintf(buf, BUFSIZ, "%s:%s", user, password); snprintf(buf, BUFSIZ, "Authorization: Basic %s\r\n", old_base64_encode(buf)); strcat(msg, buf); } #if HAVE_GSSAPI if (www_neg) { if (host) { snprintf(buf, BUFSIZ, "Authorization: Negotiate %s\r\n", GSSAPI_token(host)); strcat(msg, buf); } else 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; } #endif /* HTTP/1.0 may need keep-alive explicitly */ if (strcmp(version, "1.0") == 0 && keep_alive) strcat(msg, "Connection: keep-alive\r\n"); /* HTTP/1.1 may need close explicitly */ if (!keep_alive) strcat(msg, "Connection: close\r\n"); strcat(msg, extra_hdrs); strcat(msg, "\r\n"); } debugVerbose(1, "Request:" << std::endl << msg << std::endl << "."); uint32_t loops = Ping::Init(); 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; exit(1); } else if ((unsigned) bytesWritten != strlen(msg)) { std::cerr << "ERROR: Cannot send request?: " << std::endl << msg << std::endl; exit(1); } debugVerbose(2, "done."); if (put_file) { debugVerbose(1, "Sending HTTP request payload ..."); int x; 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; if (x <= 0) break; } if (x != 0) std::cerr << "ERROR: Cannot send file." << std::endl; else debugVerbose(1, "done."); } /* Read the data */ #if _SQUID_WINDOWS_ 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; Ping::TimerStop(fsize); } 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) { std::cerr << "SIGPIPE received." << std::endl; } static void set_our_signal(void) { #if HAVE_SIGACTION struct sigaction sa; sa.sa_handler = pipe_handler; sa.sa_flags = SA_RESTART; sigemptyset(&sa.sa_mask); if (sigaction(SIGPIPE, &sa, NULL) < 0) { std::cerr << "ERROR: Cannot set PIPE signal." << std::endl; exit(-1); } #else 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 -}