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],
$(top_builddir)/lib/libmiscencoding.la \
$(top_builddir)/lib/libmiscutil.la \
$(COMPAT_LIB) \
+ $(LIBGNUTLS_LIBS) \
$(NETTLELIB) \
$(KRB5LIBS) \
$(XTRA_LIBS)
squidclient.cc \
stub_debug.cc \
test_tools.cc \
- time.cc
+ time.cc \
+ Transport.cc \
+ Transport.h
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;
--- /dev/null
+#include "squid.h"
+#include "ip/Address.h"
+#include "tools/squidclient/Ping.h"
+#include "tools/squidclient/Transport.h"
+
+#if HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+#if HAVE_GNUTLS_X509_H
+#include <gnutls/x509.h>
+#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<const char*>(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<std::string>::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<std::string>::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
+}
--- /dev/null
+#ifndef SQUID_TOOLS_SQUIDCLIENT_TRANSPORT_H
+#define SQUID_TOOLS_SQUIDCLIENT_TRANSPORT_H
+
+#include "tools/squidclient/Parameters.h"
+
+#if HAVE_GNUTLS_GNUTLS_H
+#include <gnutls/gnutls.h>
+#endif
+#include <list>
+#include <string>
+
+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<std::string> certFiles;
+
+ // client trusted x509 certificate authorities file
+ std::list<std::string> 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 */
.
.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
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
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.
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
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 "\-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
#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 */
#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 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
{
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
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";
const char *useragent = NULL;
/* set the defaults */
- hostname = "localhost";
- localhost = NULL;
extra_hdrs[0] = '\0';
- port = CACHE_HTTP_PORT;
to_stdout = true;
reload = false;
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[] = {
{"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}
};
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;
useragent = optarg;
break;
- case 'h': /* remote host */
- hostname = optarg;
- break;
-
case 'j':
host = optarg;
break;
version = optarg;
break;
- case 'l': /* local host */
- localhost = optarg;
- break;
-
case 's': /* silent */
to_stdout = false;
break;
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;
break;
case 'T':
- io_timeout = atoi(optarg);
+ Transport::Config.ioTimeout = atoi(optarg);
break;
case 'u':
}
// 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) {
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;
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;
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;
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::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)
{
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
-}