1
0
mirror of https://github.com/squid-cache/squid.git synced 2025-04-18 22:04:07 +03:00

Remove squidclient (#1514)

Since recent commit a4e35bd removed cache_object support, popular
clients like wget and curl can do everything squidclient can, with one
exception: They cannot expand `mgr:foo` macros into
`http://localhost:3128/squid-internal-mgr/foo` URLs. That single feature
is easily emulated and not worth keeping (fairly heavy) squidclient for,
especially since recent Squid security improvements often require
customizing squidclient commands with more and more options (to address
problems tracked in Squid bug 5283).
This commit is contained in:
Francesco Chemolli 2023-10-21 09:38:56 +00:00 committed by Squid Anubis
parent 3238ca57c7
commit 80cba6d3a8
18 changed files with 7 additions and 2204 deletions

View File

@ -3092,7 +3092,6 @@ AC_CONFIG_FILES([
tools/apparmor/Makefile
tools/helper-mux/Makefile
tools/purge/Makefile
tools/squidclient/Makefile
tools/systemd/Makefile
tools/sysvinit/Makefile
])

View File

@ -44,6 +44,11 @@ The Squid-@SQUID_RELEASE@ change history can be <url url="https://github.com/squ
<tag>cache_object URI</tag>
<p>Cache manager is no longer accessible by URIs with cache_object scheme.
<tag>cachemgr</tag>
<p>The 'cachemgr' tool is no longer supported and distributed with squid.
A plethora of tools, such as curl, wget, or any web browser,
can be used in its place
<tag>non_peers</tag>
<p>Removed the <em>mgr:non_peers</em> report. Squid still ignores
unexpected ICP responses but no longer remembers the details that comprised

View File

@ -123,7 +123,7 @@ if (groff --help >/dev/null); then
if [ ! -d ${tmpdir}/doc/manuals ] ; then
mkdir -p ${tmpdir}/doc/manuals
fi
for f in `ls -1 ${tmpdir}/helpers/*/*/*.8 ${tmpdir}/src/*.8 ${tmpdir}/src/*/*.8 ${tmpdir}/tools/squidclient/*.1 ${tmpdir}/tools/*.8 ./helpers/*/*/*.8 2>/dev/null` ; do
for f in `ls -1 ${tmpdir}/helpers/*/*/*.8 ${tmpdir}/src/*.8 ${tmpdir}/src/*/*.8 ${tmpdir}/tools/*.8 ./helpers/*/*/*.8 2>/dev/null` ; do
cp $f ${tmpdir}/doc/manuals/
done
for f in `ls -1 ${tmpdir}/doc/manuals/*.1 ${tmpdir}/doc/manuals/*.8 2>/dev/null` ; do

View File

@ -49,5 +49,3 @@
[type: man] tools/cachemgr.cgi.8.in $lang:doc/manuals/$lang/cachemgr.cgi.8.in
[type: man] tools/purge/purge.1 $lang:doc/manuals/$lang/purge.1
[type: man] tools/squidclient/squidclient.1 $lang:doc/manuals/$lang/squidclient.1

View File

@ -282,7 +282,6 @@ Report ideas for new improvements to the
.
.SH SEE ALSO
.if !'po4a'hide' .B cachemgr.cgi "(8), "
.if !'po4a'hide' .B squidclient "(1), "
.if !'po4a'hide' .B basic_pam_auth "(8), "
.if !'po4a'hide' .B basic_ldap_auth "(8), "
.if !'po4a'hide' .B ext_ldap_group_acl "(8), "

View File

@ -10,7 +10,7 @@ include $(top_srcdir)/src/Common.am
## we need our local files too (but avoid -I. at all costs)
AM_CPPFLAGS += -I$(srcdir)
SUBDIRS= apparmor helper-mux purge squidclient systemd sysvinit
SUBDIRS= apparmor helper-mux purge systemd sysvinit
EXTRA_DIST=
man_MANS=
DISTCLEANFILES=

View File

@ -79,4 +79,3 @@ Report ideas for new improvements to the
.
.SH SEE ALSO
.if !'po4a'hide' .BR squid "(8), "
.if !'po4a'hide' .BR squidclient "(1)"

View File

@ -285,5 +285,4 @@ Report ideas for new improvements to the
.
.SH SEE ALSO
.if !'po4a'hide' .BR squid "(8), "
.if !'po4a'hide' .BR squidclient "(1)"
.if !'po4a'hide' .BR cachemgr.cgi "(8)"

View File

@ -1,68 +0,0 @@
## Copyright (C) 1996-2023 The Squid Software Foundation and contributors
##
## Squid software is distributed under GPLv2+ license and includes
## contributions from numerous individuals and organizations.
## Please see the COPYING and CONTRIBUTORS files for details.
##
include $(top_srcdir)/src/Common.am
SUBDIRS =
EXTRA_DIST = squidclient.1
man_MANS = squidclient.1
DISTCLEANFILES =
LDADD = \
$(top_builddir)/src/ip/libip.la \
$(top_builddir)/src/mem/libminimal.la \
$(top_builddir)/src/time/libtime.la \
$(top_builddir)/src/base/libbase.la \
$(top_builddir)/lib/libmiscencoding.la \
$(top_builddir)/lib/libmiscutil.la \
$(COMPAT_LIB) \
$(LIBGNUTLS_LIBS) \
$(LIBNETTLE_LIBS) \
$(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 $@
tests/stub_debug.cc: $(top_srcdir)/src/tests/stub_debug.cc | tests
cp $(top_srcdir)/src/tests/stub_debug.cc $@
tests/STUB.h: $(top_srcdir)/src/tests/STUB.h | tests
cp $(top_srcdir)/src/tests/STUB.h $@
tests:
mkdir -p $@
# 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 tests/stub_debug.cc tests/STUB.h
## ##### squidclient #####
bin_PROGRAMS = squidclient
squidclient_SOURCES = \
Parameters.h \
Ping.cc \
Ping.h \
Transport.cc \
Transport.h \
gssapi_support.cc \
gssapi_support.h \
squidclient.cc \
test_tools.cc
nodist_squidclient_SOURCES = \
tests/stub_debug.cc \
tests/STUB.h

View File

@ -1,38 +0,0 @@
/*
* Copyright (C) 1996-2023 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
* Please see the COPYING and CONTRIBUTORS files for details.
*/
#ifndef _SQUID_TOOLS_SQUIDCLIENT_PARAMETERS_H
#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 */

View File

@ -1,217 +0,0 @@
/*
* Copyright (C) 1996-2023 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
* Please see the COPYING and CONTRIBUTORS files for details.
*/
#include "squid.h"
#include "time/gadgets.h"
#include "tools/squidclient/Parameters.h"
#include "tools/squidclient/Ping.h"
#include <climits>
#include <csignal>
#include <iostream>
#if HAVE_GETOPT_H
#include <getopt.h>
#endif
namespace Ping
{
Ping::TheConfig Config;
/// measurements collected by the squidclient ping mode logics
class pingStats_
{
public:
pingStats_() {memset(this, 0, sizeof(pingStats_));}
long counted; ///< number of transactions which have so far been measured
long pMin; ///< shortest transaction time seen
long pMax; ///< longest transaction time seen
long sum; ///< total time so far spent waiting on transactions
} stats;
} // namespace Ping
/**
* Signal interrupt handler for squidclient ping.
* Displays final statistics and disables further pings.
*/
static void
catchSignal(int sig)
{
Ping::DisplayStats();
Ping::Config.enable = false;
std::cerr << "SIGNAL " << sig << " Interrupted." << std::endl;
}
uint32_t
Ping::Init()
{
if (Ping::Config.enable) {
#if HAVE_SIGACTION
struct sigaction sa, osa;
if (sigaction(SIGINT, nullptr, &osa) == 0 && osa.sa_handler == SIG_DFL) {
sa.sa_handler = catchSignal;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
(void) sigaction(SIGINT, &sa, nullptr);
}
#else
void (*osig) (int);
if ((osig = signal(SIGINT, catchSignal)) != SIG_DFL)
(void) signal(SIGINT, osig);
#endif
return Ping::Config.count;
}
return 1;
}
static struct timeval tv1, tv2;
void
Ping::TimerStart()
{
if (!Ping::Config.enable)
return;
#if GETTIMEOFDAY_NO_TZP
(void)gettimeofday(&tv1);
#else
(void)gettimeofday(&tv1, nullptr);
#endif
}
void
Ping::TimerStop(size_t fsize)
{
if (!Ping::Config.enable)
return;
struct tm *tmp;
time_t t2s;
long elapsed_msec;
#if GETTIMEOFDAY_NO_TZP
(void)gettimeofday(&tv2);
#else
(void)gettimeofday(&tv2, nullptr);
#endif
elapsed_msec = tvSubMsec(tv1, tv2);
t2s = tv2.tv_sec;
tmp = localtime(&t2s);
char tbuf[4096];
snprintf(tbuf, sizeof(tbuf)-1, "%d-%02d-%02d %02d:%02d:%02d [%ld]: %ld.%03ld secs, %f KB/s",
tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_mday,
tmp->tm_hour, tmp->tm_min, tmp->tm_sec, stats.counted + 1,
elapsed_msec / 1000, elapsed_msec % 1000,
elapsed_msec ? (double) fsize / elapsed_msec : -1.0);
std::cerr << tbuf << std::endl;
if (!stats.counted || elapsed_msec < stats.pMin)
stats.pMin = elapsed_msec;
if (!stats.counted || elapsed_msec > stats.pMax)
stats.pMax = elapsed_msec;
stats.sum += elapsed_msec;
++stats.counted;
/* Delay until next "ping.interval" boundary */
if (!LoopDone(stats.counted) && elapsed_msec < Ping::Config.interval) {
struct timeval tvs;
long msec_left = Ping::Config.interval - elapsed_msec;
tvs.tv_sec = msec_left / 1000;
tvs.tv_usec = (msec_left % 1000) * 1000;
select(0, nullptr, nullptr, nullptr, &tvs);
}
}
void
Ping::DisplayStats()
{
if (Ping::Config.enable && stats.counted) {
long mean = stats.sum / stats.counted;
std::cerr << std::endl
<< stats.counted << " requests, round-trip (secs) min/avg/max = "
<< (stats.pMin/1000) << "." << (stats.pMin%1000)
<< "/" << (mean/1000) << "." << (mean%1000)
<< "/" << (stats.pMax/1000) << "." << (stats.pMax%1000)
<< std::endl;
}
}
void
Ping::TheConfig::usage()
{
std::cerr << "Ping Mode" << std::endl
<< " --ping [options] Enable ping mode." << std::endl
<< std::endl
<< " options:" << std::endl
<< " -g count Ping iteration count (default, loop until interrupted)." << std::endl
<< " -I interval Ping interval in seconds (default 1 second)." << std::endl
<< std::endl;
}
bool
Ping::TheConfig::parseCommandOpts(int argc, char *argv[], int c, int &optIndex)
{
// to get here --ping was seen
enable = true;
count = 0; // default is infinite loop
interval = 1 * 1000; // default is 1s intervals
const char *shortOpStr = "g:I:?";
// options for controlling squidclient ping mode
static struct option pingOptions[] = {
{"count", no_argument, nullptr, 'g'},
{"interval", no_argument, nullptr, 'I'},
{nullptr, 0, nullptr, 0}
};
int saved_opterr = opterr;
opterr = 0; // suppress errors from getopt
while ((c = getopt_long(argc, argv, shortOpStr, pingOptions, &optIndex)) != -1) {
switch (c) {
case 'g':
if (optarg)
count = atoi(optarg);
else {
std::cerr << "ERROR: -g ping count missing parameter." << std::endl;
usage();
}
break;
case 'I':
if (!optarg) {
std::cerr << "ERROR: -I ping interval missing parameter." << std::endl;
usage();
} else if ((interval = atoi(optarg) * 1000) <= 0) {
std::cerr << "ERROR: -I ping interval out of range (0-" << (INT_MAX/1000) << ")." << std::endl;
usage();
}
break;
default:
// rewind and let the caller handle unknown options
--optind;
opterr = saved_opterr;
return true;
}
}
opterr = saved_opterr;
return false;
}

View File

@ -1,63 +0,0 @@
/*
* Copyright (C) 1996-2023 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
* Please see the COPYING and CONTRIBUTORS files for details.
*/
#ifndef _SQUID_TOOLS_CLIENT_PING_H
#define _SQUID_TOOLS_CLIENT_PING_H
/**
* API for looping the squidclient request message
* repeatedly.
*/
namespace Ping
{
/// parameters controlling 'ping' mode message looping.
class TheConfig
{
public:
TheConfig() : enable(false), count(0), interval(1*1000) {}
/// display Ping Options command line help to stderr
void usage();
/**
* parse --ping command line options
* \return true if there are other options still to parse
*/
bool parseCommandOpts(int argc, char *argv[], int c, int &optIndex);
bool enable;
int count;
int interval;
};
extern TheConfig Config;
/// initialize the squidclient ping mode
uint32_t Init();
/// whether ping loop is completed at the given iteration.
inline bool LoopDone(int i)
{
return !Ping::Config.enable || (Ping::Config.count && i >= Ping::Config.count);
}
/// start timing a new transaction
void TimerStart();
/// calculate and display the statistics for a complete transaction
/// \param fsize number of bytes transferred during this transaction (for KB/s measure)
void TimerStop(size_t fsize);
/// display summary of ping data collected
void DisplayStats();
} // namespace Ping
#endif /* _SQUID_TOOLS_CLIENT_PING_H */

View File

@ -1,531 +0,0 @@
/*
* Copyright (C) 1996-2023 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
* Please see the COPYING and CONTRIBUTORS files for details.
*/
#include "squid.h"
#include "ip/Address.h"
#include "ip/tools.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
#include <iostream>
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
<< " --https Use TLS/SSL on the HTTP connection" << std::endl
<< std::endl
<< " TLS options:" << std::endl
<< " --anonymous-tls 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
#endif
<< std::endl;
}
bool
Transport::TheConfig::parseCommandOpts(int argc, char *argv[], int c, int &optIndex)
{
bool tls = false;
const char *shortOpStr = "h:l:p:T:?";
// options for controlling squidclient transport connection
static struct option longOptions[] = {
{"anonymous-tls",no_argument, nullptr, '\1'},
{"https", no_argument, nullptr, '\3'},
{"trusted-ca", required_argument, nullptr, 'A'},
{"cert", required_argument, nullptr, 'C'},
{"host", required_argument, nullptr, 'h'},
{"local", required_argument, nullptr, 'l'},
{"port", required_argument, nullptr, 'p'},
{"params", required_argument, nullptr, 'P'},
{nullptr, 0, nullptr, 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 = nullptr;
addr.getAddrInfo(AI);
int res = bind(sock, AI->ai_addr, AI->ai_addrlen);
Ip::Address::FreeAddr(AI);
return res;
}
static void
resolveDestination(Ip::Address &iaddr)
{
struct addrinfo *AI = nullptr;
debugVerbose(2, "Transport detected: IPv4" <<
((Ip::EnableIpv6 & IPV6_SPECIAL_V4MAPPING) ? "-mapped " : "") <<
(Ip::EnableIpv6 == IPV6_OFF ? "-only" : " and IPv6") <<
((Ip::EnableIpv6 & IPV6_SPECIAL_SPLITSTACK) ? " split-stack" : ""));
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::FreeAddr(AI);
exit(1);
}
Ip::Address::FreeAddr(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 = nullptr;
addr.getAddrInfo(AI);
int res = connect(sock, AI->ai_addr, AI->ai_addrlen);
Ip::Address::FreeAddr(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(const 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
#if USE_GNUTLS
static void
gnutlsDebugHandler(int level, const char *msg)
{
debugVerbose(level, "GnuTLS: " << msg);
}
#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) {
int xerrno = errno;
std::cerr << "FATAL ERROR: TLS Initialize failed: " << xstrerr(xerrno) << std::endl;
exit(1);
}
Config.tlsEnabled = true;
#if USE_GNUTLS
gnutls_global_set_log_function(&gnutlsDebugHandler);
gnutls_global_set_log_level(scParams.verbosityLevel);
#endif
// 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 = nullptr;
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;
}
}
#else
(void)hostname;
#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
}

View File

@ -1,121 +0,0 @@
/*
* Copyright (C) 1996-2023 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
* Please see the COPYING and CONTRIBUTORS files for details.
*/
#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(nullptr),
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 connection
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(const 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 */

View File

@ -1,159 +0,0 @@
/*
* Copyright (C) 1996-2023 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
* Please see the COPYING and CONTRIBUTORS files for details.
*/
#include "squid.h"
#if HAVE_GSSAPI
#include "base64.h"
#include "tools/squidclient/gssapi_support.h"
#include <iostream>
#if !defined(gss_mech_spnego)
static gss_OID_desc _gss_mech_spnego = {6, (void *) "\x2b\x06\x01\x05\x05\x02"};
gss_OID gss_mech_spnego = &_gss_mech_spnego;
#endif
#define BUFFER_SIZE 8192
/**
* Check return valuse major_status, minor_status for error and print error description
* in case of an error.
*
* \retval true in case of gssapi error
* \retval false in case of no gssapi error
*/
bool
check_gss_err(OM_uint32 major_status, OM_uint32 minor_status, const char *function)
{
if (GSS_ERROR(major_status)) {
OM_uint32 maj_stat, min_stat;
OM_uint32 msg_ctx = 0;
gss_buffer_desc status_string;
char buf[BUFFER_SIZE];
size_t len;
len = 0;
msg_ctx = 0;
while (!msg_ctx) {
/* convert major status code (GSS-API error) to text */
maj_stat = gss_display_status(&min_stat, major_status,
GSS_C_GSS_CODE,
GSS_C_NULL_OID,
&msg_ctx, &status_string);
if (maj_stat == GSS_S_COMPLETE) {
snprintf(buf + len, BUFFER_SIZE-len, "%s", (char *) status_string.value);
len += status_string.length;
gss_release_buffer(&min_stat, &status_string);
break;
}
gss_release_buffer(&min_stat, &status_string);
}
snprintf(buf + len, BUFFER_SIZE-len, "%s", ". ");
len += 2;
msg_ctx = 0;
while (!msg_ctx) {
/* convert minor status code (underlying routine error) to text */
maj_stat = gss_display_status(&min_stat, minor_status,
GSS_C_MECH_CODE,
GSS_C_NULL_OID,
&msg_ctx, &status_string);
if (maj_stat == GSS_S_COMPLETE) {
snprintf(buf + len, BUFFER_SIZE-len,"%s", (char *) status_string.value);
len += status_string.length;
gss_release_buffer(&min_stat, &status_string);
break;
}
gss_release_buffer(&min_stat, &status_string);
}
std::cerr << "ERROR: " << function << " failed: " << buf << std::endl;
return true;
}
return false;
}
/**
* Get gssapi token for service HTTP/<server>
* User has to initiate a kinit user@DOMAIN on commandline first for the
* function to be successful
*
* \return base64 encoded token if successful,
* string "ERROR" if unsuccessful
*/
char *
GSSAPI_token(const char *server)
{
OM_uint32 major_status, minor_status;
gss_ctx_id_t gss_context = GSS_C_NO_CONTEXT;
gss_name_t server_name = GSS_C_NO_NAME;
gss_buffer_desc service = GSS_C_EMPTY_BUFFER;
gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER;
gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER;
char *token = nullptr;
setbuf(stdout, nullptr);
setbuf(stdin, nullptr);
if (!server) {
std::cerr << "ERROR: GSSAPI: No server name" << std::endl;
token = new char[6];
memcpy(token, "ERROR", 5);
token[5] = '\0';
return token;
}
service.value = xmalloc(strlen("HTTP") + strlen(server) + 2);
snprintf((char *) service.value, strlen("HTTP") + strlen(server) + 2, "%s@%s", "HTTP", server);
service.length = strlen((char *) service.value);
major_status = gss_import_name(&minor_status, &service,
gss_nt_service_name, &server_name);
if (!check_gss_err(major_status, minor_status, "gss_import_name()")) {
major_status = gss_init_sec_context(&minor_status,
GSS_C_NO_CREDENTIAL,
&gss_context,
server_name,
gss_mech_spnego,
0,
0,
GSS_C_NO_CHANNEL_BINDINGS,
&input_token,
nullptr,
&output_token,
nullptr,
nullptr);
if (!check_gss_err(major_status, minor_status, "gss_init_sec_context()") && output_token.length) {
token = new char[base64_encode_len(output_token.length)];
struct base64_encode_ctx ctx;
base64_encode_init(&ctx);
size_t blen = base64_encode_update(&ctx, token, output_token.length, reinterpret_cast<const uint8_t*>(output_token.value));
blen += base64_encode_final(&ctx, token+blen);
token[blen] = '\0';
}
}
if (!output_token.length) {
token = new char[6];
memcpy(token, "ERROR", 5);
token[5] = '\0';
}
gss_delete_sec_context(&minor_status, &gss_context, nullptr);
gss_release_buffer(&minor_status, &service);
gss_release_buffer(&minor_status, &input_token);
gss_release_buffer(&minor_status, &output_token);
gss_release_name(&minor_status, &server_name);
return token;
}
#endif /* HAVE_GSSAPI */

View File

@ -1,53 +0,0 @@
/*
* Copyright (C) 1996-2023 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
* Please see the COPYING and CONTRIBUTORS files for details.
*/
#ifndef _SQUID_TOOLS_SQUIDCLIENT_GSSAPI_H
#define _SQUID_TOOLS_SQUIDCLIENT_GSSAPI_H
#if HAVE_GSSAPI
#if USE_APPLE_KRB5
#define GSSKRB_APPLE_DEPRECATED(x)
#endif
#if USE_HEIMDAL_KRB5
#if HAVE_GSSAPI_GSSAPI_H
#include <gssapi/gssapi.h>
#elif HAVE_GSSAPI_H
#include <gssapi.h>
#endif /* HAVE_GSSAPI_GSSAPI_H/HAVE_GSSAPI_H */
#elif USE_GNUGSS
#if HAVE_GSS_H
#include <gss.h>
#endif
#else
#if HAVE_GSSAPI_GSSAPI_H
#include <gssapi/gssapi.h>
#elif HAVE_GSSAPI_H
#include <gssapi.h>
#endif /* HAVE_GSSAPI_GSSAPI_H/HAVE_GSSAPI_H */
#if HAVE_GSSAPI_GSSAPI_KRB5_H
#include <gssapi/gssapi_krb5.h>
#endif
#if HAVE_GSSAPI_GSSAPI_GENERIC_H
#include <gssapi/gssapi_generic.h>
#endif
#if HAVE_GSSAPI_GSSAPI_EXT_H
#include <gssapi/gssapi_ext.h>
#endif
#endif
#ifndef gss_nt_service_name
#define gss_nt_service_name GSS_C_NT_HOSTBASED_SERVICE
#endif
bool check_gss_err(OM_uint32 major_status, OM_uint32 minor_status, const char *function);
char *GSSAPI_token(const char *server);
#endif /* HAVE_GSSAPI */
#endif /* _SQUID_TOOLS_SQUIDCLIENT_GSSAPI_H */

View File

@ -1,264 +0,0 @@
.if !'po4a'hide' .TH squidclient 1
.
.SH NAME
squidclient \- A simple HTTP web client tool
.
.SH SYNOPSIS
.if !'po4a'hide' .B squidclient
.if !'po4a'hide' .B "[ \-aknNrsv ] "
.if !'po4a'hide' .B "[ \-\-ping [ping\-options] ] "
.if !'po4a'hide' .B "[ \-\-https] [tls\-options] [ \-A"
string
.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 "] [ \-m"
method
.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
.
.PP
.if !'po4a'hide' .B "Ping options: [ \-g"
count
.if !'po4a'hide' .B "] [ \-I"
interval
.if !'po4a'hide' .B "] "
.
.PP
.if !'po4a'hide' .B "TLS options: [ \-\-anonymous\-tls ] [ \-\-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.
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 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 '\en'
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 | \-\-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
.
.if !'po4a'hide' .TP
.if !'po4a'hide' .B "\-n"
Proxy Negotiate(Kerberos) authentication.
.if !'po4a'hide' .nf
Use kinit username@DOMAIN first to get initial TGS.
.
.if !'po4a'hide' .TP
.if !'po4a'hide' .B "\-N"
WWW Negotiate(Kerberos) authentication.
.if !'po4a'hide' .nf
Use kinit username@DOMAIN first to get initial TGS.
.if !'po4a'hide' .fi
.
.if !'po4a'hide' .TP
.if !'po4a'hide' .B "\-p port"
Port number of cache. Default is 3128.
.
.if !'po4a'hide' .TP
.if !'po4a'hide' .B "\-P file"
Request body. Using the named file as data.
.
.if !'po4a'hide' .TP
.if !'po4a'hide' .B "\-r"
Force cache to reload URL.
.
.if !'po4a'hide' .TP
.if !'po4a'hide' .B "\-s"
Silent. Do not print data to stdout.
.
.if !'po4a'hide' .TP
.if !'po4a'hide' .B "\-t count"
Trace
.I count
HTTP relay or proxy hops
.
.if !'po4a'hide' .TP
.if !'po4a'hide' .B "\-T timeout"
Timeout value (seconds) for read/write operations.
.
.if !'po4a'hide' .TP
.if !'po4a'hide' .B "\-u user"
Proxy authentication username
.
.if !'po4a'hide' .TP
.if !'po4a'hide' .B "\-U user"
WWW authentication username
.
.if !'po4a'hide' .TP
.if !'po4a'hide' .B "\-v"
Verbose. Print outgoing message to stderr.
.
.if !'po4a'hide' .TP
.if !'po4a'hide' .B "\-V version"
HTTP Version. Use '\-' for HTTP/0.9 omitted case
.
.if !'po4a'hide' .TP
.if !'po4a'hide' .B "\-w password"
Proxy authentication password
.
.if !'po4a'hide' .TP
.if !'po4a'hide' .B "\-W password"
WWW authentication password
.
.if !'po4a'hide' .TP
.if !'po4a'hide' .B "\-\-https"
Use Transport Layer Security on the HTTP connection.
.
.if !'po4a'hide' .TP
.if !'po4a'hide' .B "\-\-anonymous\-tls"
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
.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
.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.
.
.if !'po4a'hide' .TP
.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
.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).
.
.SH AUTHOR
This program and manual was written by
.if !'po4a'hide' .I Amos Jeffries <amosjeffries@squid-cache.org>
.PP
Based on original code derived from Harvest and further developed by
numerous individuals from the internet community.
.
.SH COPYRIGHT
.PP
* Copyright (C) 1996-2023 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
* Please see the COPYING and CONTRIBUTORS files for details.
.
.SH QUESTIONS
Questions on the usage of this program can be sent to the
.I Squid Users mailing list
.if !'po4a'hide' <squid-users@lists.squid-cache.org>
.
.SH REPORTING BUGS
See https://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 https://bugs.squid-cache.org/
.PP
Report serious security bugs to
.I Squid Bugs <squid-bugs@lists.squid-cache.org>
.PP
Report ideas for new improvements to the
.I Squid Developers mailing list
.if !'po4a'hide' <squid-dev@lists.squid-cache.org>
.
.SH SEE ALSO
.if !'po4a'hide' .BR squid "(8), "
.if !'po4a'hide' .BR cachemgr.cgi "(8)"

View File

@ -1,682 +0,0 @@
/*
* Copyright (C) 1996-2023 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
* Please see the COPYING and CONTRIBUTORS files for details.
*/
#include "squid.h"
#include "base64.h"
#include "ip/Address.h"
#include "ip/tools.h"
#include "time/gadgets.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 <cassert>
#include <cerrno>
#include <csignal>
#include <cstring>
#include <iostream>
#include <sstream>
#if _SQUID_WINDOWS_
#include <io.h>
#endif
#if HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#if HAVE_NETDB_H
#include <netdb.h>
#endif
#if HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
#if HAVE_FCNTL_H
#include <fcntl.h>
#endif
#if HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#if HAVE_GETOPT_H
#include <getopt.h>
#endif
#ifndef BUFSIZ
#define BUFSIZ 8192
#endif
/* Local functions */
static void usage(const char *progname);
void pipe_handler(int sig);
static void set_our_signal(void);
Parameters scParams;
static int put_fd;
static char *put_file = nullptr;
static struct stat sb;
int total_bytes = 0;
#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 = nullptr;
#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;
std::cerr
<< " -s | --quiet Silent. Do not print response message to stdout." << 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. Supports '\\\\', '\\n', '\\r' and '\\t'." << 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(EXIT_FAILURE);
}
static void
shellUnescape(char *buf)
{
if (!buf)
return;
unsigned char *p, *d;
d = p = reinterpret_cast<unsigned char *>(buf);
while (auto ch = *p) {
if (ch == '\\') {
++p;
switch (*p) {
case 'n':
ch = '\n';
break;
case 'r':
ch = '\r';
break;
case 't':
ch = '\t';
break;
case '\\':
ch = '\\';
break;
default:
ch = *p;
debugVerbose(1, "Warning: unsupported shell code '\\" << ch << "'");
break;
}
*d = ch;
if (!ch)
continue;
} else {
*d = *p;
}
++p;
++d;
}
*d = '\0';
}
/// [Proxy-]Authorization header producer
class Authorization
{
public:
Authorization(const char *aHeader, const char *aDestination):
header(aHeader), destination(aDestination) {}
/// finalizes and writes the right HTTP header to the given stream
void commit(std::ostream &os);
std::string header; ///< HTTP header name to send
std::string destination; ///< used when describing password
const char *user = nullptr; ///< user name to encode and send
const char *password = nullptr; ///< user password to encode and send
};
void
Authorization::commit(std::ostream &os)
{
#if HAVE_GETPASS
if (!password)
password = getpass((destination + " password: ").c_str());
#endif
if (!password) {
std::cerr << "ERROR: " << destination << " password missing\n";
exit(EXIT_FAILURE);
}
struct base64_encode_ctx ctx;
base64_encode_init(&ctx);
const auto bcapacity = base64_encode_len(strlen(user) + 1 + strlen(password));
const auto buf = new char[bcapacity];
size_t bsize = 0;
bsize += base64_encode_update(&ctx, buf, strlen(user), reinterpret_cast<const uint8_t*>(user));
bsize += base64_encode_update(&ctx, buf+bsize, 1, reinterpret_cast<const uint8_t*>(":"));
bsize += base64_encode_update(&ctx, buf+bsize, strlen(password), reinterpret_cast<const uint8_t*>(password));
bsize += base64_encode_final(&ctx, buf+bsize);
assert(bsize <= bcapacity); // paranoid and late but better than nothing
os << header << ": Basic ";
os.write(buf, bsize);
os << "\r\n";
delete[] buf;
}
static Authorization ProxyAuthorization("Proxy-Authorization", "proxy");
static Authorization OriginAuthorization("Authorization", "origin server");
int
main(int argc, char *argv[])
{
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
char url[BUFSIZ];
char buf[BUFSIZ];
char *extra_hdrs = nullptr;
const char *method = "GET";
extern char *optarg;
time_t ims = 0;
int max_forwards = -1;
const char *host = nullptr;
const char *version = "1.0";
const char *useragent = nullptr;
/* set the defaults */
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], sizeof(url));
url[sizeof(url) - 1] = '\0';
int optIndex = 0;
const char *shortOpStr = "aA:h:j:V:l:P:i:km:nNp: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, nullptr, '?'},
{"verbose", no_argument, nullptr, 'v'},
{"quiet", no_argument, nullptr, 's'},
{"host", required_argument, nullptr, 'h'},
{"local", required_argument, nullptr, 'l'},
{"port", required_argument, nullptr, 'p'},
{"ping", no_argument, nullptr, '\1'},
{"https", no_argument, nullptr, '\3'},
{nullptr, 0, nullptr, 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;
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;
Transport::Config.parseCommandOpts(argc, argv, c, optIndex);
continue;
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 'j':
host = optarg;
break;
case 'V':
version = 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':
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)) {
if (extra_hdrs) {
std::cerr << "ERROR: multiple -H options not supported. Discarding previous value." << std::endl;
xfree(extra_hdrs);
}
extra_hdrs = xstrdup(optarg);
shellUnescape(extra_hdrs);
}
break;
case 'T':
Transport::Config.ioTimeout = atoi(optarg);
break;
case 'u':
ProxyAuthorization.user = optarg;
break;
case 'w':
ProxyAuthorization.password = optarg;
break;
case 'U':
OriginAuthorization.user = optarg;
break;
case 'W':
OriginAuthorization.password = optarg;
break;
case 'n':
#if HAVE_GSSAPI
proxy_neg = 1;
#else
std::cerr << "ERROR: Negotiate authentication not supported." << std::endl;
usage(argv[0]);
#endif
break;
case 'N':
#if HAVE_GSSAPI
www_neg = 1;
#else
std::cerr << "ERROR: Negotiate authentication not supported." << std::endl;
usage(argv[0]);
#endif
break;
case 'v':
/* undocumented: may increase verb-level by giving more -v's */
++scParams.verbosityLevel;
debugVerbose(2, "verbosity level set to " << scParams.verbosityLevel);
break;
case '?': /* usage */
default:
usage(argv[0]);
break;
}
}
if (ProxyAuthorization.password && !ProxyAuthorization.user) {
std::cerr << "ERROR: Proxy authentication password (-w) is given, but username (-u) is missing\n";
exit(EXIT_FAILURE);
}
if (OriginAuthorization.password && !OriginAuthorization.user) {
std::cerr << "ERROR: WWW authentication password (-W) is given, but username (-U) is missing\n";
exit(EXIT_FAILURE);
}
}
#if _SQUID_WINDOWS_
{
WSADATA wsaData;
WSAStartup(2, &wsaData);
atexit(Win32SockCleanup);
}
#endif
/* Build the HTTP request */
const char *pathPassword = nullptr;
if (strncmp(url, "mgr:", 4) == 0) {
char *t = xstrdup(url + 4);
// XXX: Bail on snprintf() failures
snprintf(url, sizeof(url), "http://%s:%hu/squid-internal-mgr/%s", Transport::Config.hostname, Transport::Config.port, t);
if (const auto at = strrchr(url, '@')) {
if (!OriginAuthorization.user) {
std::cerr << "ERROR: Embedding a password in a cache manager command requires " <<
"providing a username with -U: mgr:" << t << std::endl;
exit(EXIT_FAILURE);
}
*at = 0; // send password in Authorization header, not URL
pathPassword = at + 1; // the now-removed embedded @password overwrites OriginAuthorization.password further below
}
xfree(t);
}
if (put_file) {
put_fd = open(put_file, O_RDONLY);
set_our_signal();
if (put_fd < 0) {
int xerrno = errno;
std::cerr << "ERROR: can't open file (" << xstrerr(xerrno) << ")" << std::endl;
exit(EXIT_FAILURE);
}
#if _SQUID_WINDOWS_
setmode(put_fd, O_BINARY);
#endif
if (fstat(put_fd, &sb) < 0) {
int xerrno = errno;
std::cerr << "ERROR: can't identify length of file (" << xstrerr(xerrno) << ")" << std::endl;
}
}
if (!host) {
char *newhost = strstr(url, "://");
if (newhost) {
char *t;
newhost += 3;
newhost = xstrdup(newhost);
t = newhost + strcspn(newhost, "@/?");
if (*t == '@') {
newhost = t + 1;
t = newhost + strcspn(newhost, "@/?");
}
*t = '\0';
host = newhost;
}
}
std::stringstream msg;
if (version[0] == '-' || !version[0]) {
/* HTTP/0.9, no headers, no version */
msg << method << " " << url << "\r\n";
} else {
const auto versionImpliesHttp = xisdigit(version[0]); // is HTTP/n.n
msg << method << " "
<< url << " "
<< (versionImpliesHttp ? "HTTP/" : "") << version
<< "\r\n";
if (host) {
msg << "Host: " << host << "\r\n";
}
if (!useragent) {
msg << "User-Agent: squidclient/" << VERSION << "\r\n";
} else if (useragent[0] != '\0') {
msg << "User-Agent: " << useragent << "\r\n";
} // else custom: no value U-A header
if (reload) {
msg << "Cache-Control: no-cache\r\n";
}
if (put_fd > 0) {
msg << "Content-length: " << sb.st_size << "\r\n";
}
if (opt_noaccept == 0) {
msg << "Accept: */*\r\n";
}
if (ims) {
msg << "If-Modified-Since: " << Time::FormatRfc1123(ims) << "\r\n";
}
if (max_forwards > -1) {
msg << "Max-Forwards: " << max_forwards << "\r\n";
}
if (ProxyAuthorization.user)
ProxyAuthorization.commit(msg);
if (OriginAuthorization.user) {
const auto savedPassword = OriginAuthorization.password;
if (pathPassword)
OriginAuthorization.password = pathPassword;
OriginAuthorization.commit(msg);
OriginAuthorization.password = savedPassword; // restore the global password setting
}
#if HAVE_GSSAPI
if (www_neg) {
if (host) {
const char *token = GSSAPI_token(host);
msg << "Proxy-Authorization: Negotiate " << token << "\r\n";
delete[] token;
} else
std::cerr << "ERROR: server host missing" << std::endl;
}
if (proxy_neg) {
if (Transport::Config.hostname) {
const char *token = GSSAPI_token(Transport::Config.hostname);
msg << "Proxy-Authorization: Negotiate " << token << "\r\n";
delete[] token;
} 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)
msg << "Connection: keep-alive\r\n";
/* HTTP/1.1 may need close explicitly */
if (!keep_alive)
msg << "Connection: close\r\n";
if (extra_hdrs) {
msg << extra_hdrs;
safe_free(extra_hdrs);
}
msg << "\r\n"; // empty line ends MIME header block
}
msg.flush();
const auto messageHeader = msg.str();
debugVerbose(1, "Request:" << std::endl << messageHeader << std::endl << ".");
uint32_t loops = Ping::Init();
for (uint32_t i = 0; loops == 0 || i < loops; ++i) {
size_t fsize = 0;
if (!Transport::Connect())
continue;
/* Send the HTTP request */
debugVerbose(2, "Sending HTTP request ... ");
bytesWritten = Transport::Write(messageHeader.data(), messageHeader.length());
if (bytesWritten < 0) {
std::cerr << "ERROR: write" << std::endl;
exit(EXIT_FAILURE);
} else if (static_cast<size_t>(bytesWritten) != messageHeader.length()) {
std::cerr << "ERROR: Failed to send the following request: " << std::endl
<< messageHeader << std::endl;
exit(EXIT_FAILURE);
}
debugVerbose(2, "done.");
if (put_file) {
debugVerbose(1, "Sending HTTP request payload ...");
int x;
if ((x = lseek(put_fd, 0, SEEK_SET)) < 0) {
int xerrno = errno;
std::cerr << "ERROR: lseek: " << xstrerr(xerrno) << std::endl;
} else while ((x = read(put_fd, buf, sizeof(buf))) > 0) {
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 = Transport::Read(buf, sizeof(buf))) > 0) {
fsize += len;
if (to_stdout && fwrite(buf, len, 1, stdout) != 1) {
int xerrno = errno;
std::cerr << "ERROR: writing to stdout: " << xstrerr(xerrno) << 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
Transport::CloseConnection();
if (Ping::LoopDone(i))
break;
Ping::TimerStop(fsize);
}
Ping::DisplayStats();
Transport::ShutdownTls();
return EXIT_SUCCESS;
}
void
pipe_handler(int)
{
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, nullptr) < 0) {
std::cerr << "ERROR: Cannot set PIPE signal." << std::endl;
exit(EXIT_FAILURE);
}
#else
signal(SIGPIPE, pipe_handler);
#endif
}