2 * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
10 #include "ip/Address.h"
12 #include "tools/squidclient/Ping.h"
13 #include "tools/squidclient/Transport.h"
18 #if HAVE_GNUTLS_X509_H
19 #include <gnutls/x509.h>
23 Transport::TheConfig
Transport::Config
;
25 /// the current server connection FD
29 Transport::TheConfig::usage()
31 std::cerr
<< "Connection Settings" << std::endl
32 << " -h | --host host Send message to server on 'host'. Default is localhost." << std::endl
33 << " -l | --local host Specify a local IP address to bind to. Default is none." << std::endl
34 << " -p | --port port Port number on server to contact. Default is " << CACHE_HTTP_PORT
<< "." << std::endl
35 << " -T timeout Timeout in seconds for read/write operations" << std::endl
37 << " --https Use TLS/SSL on the HTTP connection" << std::endl
39 << " TLS options:" << std::endl
40 << " --anonymous-tls Use Anonymous TLS. Sets default parameters:" << std::endl
41 << " \"PERFORMANCE:+ANON-ECDH:+ANON-DH\"" << std::endl
42 << " --params=\"...\" Use the given parameters." << std::endl
43 << " --cert=FILE Path to a PEM file holding the client X.509 certificate chain." << std::endl
44 << " May be repeated if there are multiple certificates to use for the server." << std::endl
45 << " --trusted-ca=PATH Path to a PEM file holding trusted CA certificate(s)." << std::endl
46 << " May be repeated." << std::endl
47 << " Example path: \"/etc/ssl/certs/ca-certificates.crt\"" << std::endl
53 Transport::TheConfig::parseCommandOpts(int argc
, char *argv
[], int c
, int &optIndex
)
56 const char *shortOpStr
= "h:l:p:T:?";
58 // options for controlling squidclient transport connection
59 static struct option longOptions
[] = {
60 {"anonymous-tls",no_argument
, 0, '\1'},
61 {"https", no_argument
, 0, '\3'},
62 {"trusted-ca", required_argument
, 0, 'A'},
63 {"cert", required_argument
, 0, 'C'},
64 {"host", required_argument
, 0, 'h'},
65 {"local", required_argument
, 0, 'l'},
66 {"port", required_argument
, 0, 'p'},
67 {"params", required_argument
, 0, 'P'},
71 int saved_opterr
= opterr
;
72 opterr
= 0; // suppress errors from getopt
78 params
= "PERFORMANCE:+ANON-ECDH:+ANON-DH";
87 caFiles
.push_back(std::string(optarg
));
92 certFiles
.push_back(std::string(optarg
));
103 case 'p': /* port number */
104 sscanf(optarg
, "%hd", &port
);
106 port
= CACHE_HTTP_PORT
; /* default */
115 ioTimeout
= atoi(optarg
);
120 Transport::InitTls();
122 // rewind and let the caller handle unknown options
124 opterr
= saved_opterr
;
127 } while ((c
= getopt_long(argc
, argv
, shortOpStr
, longOptions
, &optIndex
)) != -1);
130 Transport::InitTls();
132 opterr
= saved_opterr
;
136 /// Set up the source socket address from which to send.
138 client_comm_bind(int sock
, const Ip::Address
&addr
)
140 static struct addrinfo
*AI
= NULL
;
141 addr
.getAddrInfo(AI
);
142 int res
= bind(sock
, AI
->ai_addr
, AI
->ai_addrlen
);
143 Ip::Address::FreeAddr(AI
);
148 resolveDestination(Ip::Address
&iaddr
)
150 struct addrinfo
*AI
= NULL
;
152 debugVerbose(2, "Transport detected: IPv4" <<
153 ((Ip::EnableIpv6
& IPV6_SPECIAL_V4MAPPING
) ? "-mapped " : "") <<
154 (Ip::EnableIpv6
== IPV6_OFF
? "-only" : " and IPv6") <<
155 ((Ip::EnableIpv6
& IPV6_SPECIAL_SPLITSTACK
) ? " split-stack" : ""));
157 if (Transport::Config
.localHost
) {
158 debugVerbose(2, "Resolving " << Transport::Config
.localHost
<< " ...");
160 if ( !iaddr
.GetHostByName(Transport::Config
.localHost
) ) {
161 std::cerr
<< "ERROR: Cannot resolve " << Transport::Config
.localHost
<< ": Host unknown." << std::endl
;
165 debugVerbose(2, "Resolving " << Transport::Config
.hostname
<< " ...");
166 /* Process the remote host name to locate the Protocol required
167 in case we are being asked to link to another version of squid */
168 if ( !iaddr
.GetHostByName(Transport::Config
.hostname
) ) {
169 std::cerr
<< "ERROR: Cannot resolve " << Transport::Config
.hostname
<< ": Host unknown." << std::endl
;
174 iaddr
.getAddrInfo(AI
);
175 if ((conn
= socket(AI
->ai_family
, AI
->ai_socktype
, 0)) < 0) {
176 std::cerr
<< "ERROR: could not open socket to " << iaddr
<< std::endl
;
177 Ip::Address::FreeAddr(AI
);
180 Ip::Address::FreeAddr(AI
);
182 if (Transport::Config
.localHost
) {
183 if (client_comm_bind(conn
, iaddr
) < 0) {
184 std::cerr
<< "ERROR: could not bind socket to " << iaddr
<< std::endl
;
190 debugVerbose(2, "Resolving... " << Transport::Config
.hostname
);
192 if ( !iaddr
.GetHostByName(Transport::Config
.hostname
) ) {
193 std::cerr
<< "ERROR: Cannot resolve " << Transport::Config
.hostname
<< ": Host unknown." << std::endl
;
198 iaddr
.port(Transport::Config
.port
);
201 /// Set up the destination socket address for message to send to.
203 client_comm_connect(int sock
, const Ip::Address
&addr
)
205 static struct addrinfo
*AI
= NULL
;
206 addr
.getAddrInfo(AI
);
207 int res
= connect(sock
, AI
->ai_addr
, AI
->ai_addrlen
);
208 Ip::Address::FreeAddr(AI
);
217 resolveDestination(iaddr
);
219 debugVerbose(2, "Connecting... " << Config
.hostname
<< " (" << iaddr
<< ")");
221 if (client_comm_connect(conn
, iaddr
) < 0) {
222 char hostnameBuf
[MAX_IPSTRLEN
];
223 iaddr
.toUrl(hostnameBuf
, MAX_IPSTRLEN
);
224 std::cerr
<< "ERROR: Cannot connect to " << hostnameBuf
225 << (!errno
?": Host unknown." : "") << std::endl
;
228 debugVerbose(2, "Connected to: " << Config
.hostname
<< " (" << iaddr
<< ")");
230 // do any TLS setup that might be needed
231 if (!Transport::MaybeStartTls(Config
.hostname
))
238 Transport::Write(void *buf
, size_t len
)
243 if (Config
.tlsEnabled
) {
245 gnutls_record_send(Config
.session
, buf
, len
);
253 return send(conn
, buf
, len
, 0);
255 alarm(Config
.ioTimeout
);
256 return write(conn
, buf
, len
);
262 Transport::Read(void *buf
, size_t len
)
267 if (Config
.tlsEnabled
) {
269 return gnutls_record_recv(Config
.session
, buf
, len
);
276 return recv(conn
, buf
, len
, 0);
278 alarm(Config
.ioTimeout
);
279 return read(conn
, buf
, len
);
285 Transport::CloseConnection()
292 /* This function will verify the peer's certificate, and check
293 * if the hostname matches, as well as the activation, expiration dates.
296 verifyByCA(gnutls_session_t session
)
299 const char *hostname
= static_cast<const char*>(gnutls_session_get_ptr(session
));
301 /* This verification function uses the trusted CAs in the credentials
302 * structure. So you must have installed one or more CA certificates.
305 if (gnutls_certificate_verify_peers3(session
, hostname
, &status
) < 0) {
306 std::cerr
<< "VERIFY peers failure";
307 return GNUTLS_E_CERTIFICATE_ERROR
;
310 gnutls_certificate_type_t type
= gnutls_certificate_type_get(session
);
312 if (gnutls_certificate_verification_status_print(status
, type
, &out
, 0) < 0) {
313 std::cerr
<< "VERIFY status failure";
314 return GNUTLS_E_CERTIFICATE_ERROR
;
317 std::cerr
<< "VERIFY DATUM: " << out
.data
<< std::endl
;
318 gnutls_free(out
.data
);
320 if (status
!= 0) /* Certificate is not trusted */
321 return GNUTLS_E_CERTIFICATE_ERROR
;
323 /* notify gnutls to continue handshake normally */
324 return GNUTLS_E_SUCCESS
;
328 verifyTlsCertificate(gnutls_session_t session
)
330 // XXX: 1) try to verify using DANE -> Secure Authenticated Connection
332 // 2) try to verify using CA
333 if (verifyByCA(session
) == GNUTLS_E_SUCCESS
) {
334 std::cerr
<< "SUCCESS: CA verified Encrypted Connection" << std::endl
;
335 return GNUTLS_E_SUCCESS
;
338 // 3) fails both is insecure, but show the results anyway.
339 std::cerr
<< "WARNING: Insecure Connection" << std::endl
;
340 return GNUTLS_E_SUCCESS
;
348 debugVerbose(3, "Initializing TLS library...");
349 // NP: gnutls init is re-entrant and lock-counted with deinit but not thread safe.
350 if (gnutls_global_init() != GNUTLS_E_SUCCESS
) {
351 std::cerr
<< "FATAL ERROR: TLS Initialize failed: " << xstrerror() << std::endl
;
355 Config
.tlsEnabled
= true;
357 // Initialize for anonymous TLS
358 gnutls_anon_allocate_client_credentials(&Config
.anonCredentials
);
360 // Initialize for X.509 certificate exchange
361 gnutls_certificate_allocate_credentials(&Config
.certCredentials
);
362 for (std::list
<std::string
>::const_iterator i
= Config
.caFiles
.begin(); i
!= Config
.caFiles
.end(); ++i
) {
363 int x
= gnutls_certificate_set_x509_trust_file(Config
.certCredentials
, (*i
).c_str(), GNUTLS_X509_FMT_PEM
);
365 debugVerbose(3, "WARNING: Failed to load Certificate Authorities from " << *i
);
367 debugVerbose(3, "Loaded " << x
<< " Certificate Authorities from " << *i
);
370 gnutls_certificate_set_verify_function(Config
.certCredentials
, verifyTlsCertificate
);
372 for (std::list
<std::string
>::const_iterator i
= Config
.certFiles
.begin(); i
!= Config
.certFiles
.end(); ++i
) {
373 if (gnutls_certificate_set_x509_key_file(Transport::Config
.certCredentials
, (*i
).c_str(), (*i
).c_str(), GNUTLS_X509_FMT_PEM
) != GNUTLS_E_SUCCESS
) {
374 debugVerbose(3, "WARNING: Failed to load Certificate from " << *i
);
376 debugVerbose(3, "Loaded Certificate from " << *i
);
381 std::cerr
<< "ERROR: TLS support not available." << std::endl
;
387 // perform the actual handshake exchange with remote server
389 doTlsHandshake(const char *type
)
391 // setup the connection for TLS
392 gnutls_transport_set_int(Transport::Config
.session
, conn
);
393 gnutls_handshake_set_timeout(Transport::Config
.session
, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT
);
395 debugVerbose(2, type
<< " TLS handshake ... ");
399 ret
= gnutls_handshake(Transport::Config
.session
);
400 } while (ret
< 0 && gnutls_error_is_fatal(ret
) == 0);
403 std::cerr
<< "ERROR: " << type
<< " TLS Handshake failed (" << ret
<< ") "
404 << gnutls_alert_get_name(gnutls_alert_get(Transport::Config
.session
))
407 gnutls_deinit(Transport::Config
.session
);
411 char *desc
= gnutls_session_get_desc(Transport::Config
.session
);
412 debugVerbose(3, "TLS Session info: " << std::endl
<< desc
<< std::endl
);
420 const char *err
= NULL
;
422 if ((x
= gnutls_priority_set_direct(Transport::Config
.session
, Transport::Config
.params
, &err
)) != GNUTLS_E_SUCCESS
) {
423 if (x
== GNUTLS_E_INVALID_REQUEST
)
424 std::cerr
<< "ERROR: Syntax error at: " << err
<< std::endl
;
431 // attempt an anonymous TLS handshake
432 // this encrypts the connection but does not secure it
433 // so many public servers do not support this handshake type.
437 if (!loadTlsParameters())
440 // put the anonymous credentials to the current session
442 if ((x
= gnutls_credentials_set(Transport::Config
.session
, GNUTLS_CRD_ANON
, Transport::Config
.anonCredentials
)) != GNUTLS_E_SUCCESS
) {
443 std::cerr
<< "ERROR: Anonymous TLS credentials setup failed (" << x
<< ") " << std::endl
;
448 return doTlsHandshake("Anonymous");
451 // attempt a X.509 certificate exchange
452 // this both encrypts and authenticates the connection
454 tryTlsCertificate(const char *hostname
)
456 gnutls_session_set_ptr(Transport::Config
.session
, (void *) hostname
);
457 gnutls_server_name_set(Transport::Config
.session
, GNUTLS_NAME_DNS
, hostname
, strlen(hostname
));
459 if (!loadTlsParameters())
462 // put the X.509 credentials to the current session
463 gnutls_credentials_set(Transport::Config
.session
, GNUTLS_CRD_CERTIFICATE
, Transport::Config
.certCredentials
);
465 // setup the connection for TLS
466 gnutls_transport_set_int(Transport::Config
.session
, conn
);
467 gnutls_handshake_set_timeout(Transport::Config
.session
, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT
);
469 return doTlsHandshake("X.509");
474 Transport::MaybeStartTls(const char *hostname
)
477 if (Config
.tlsEnabled
) {
479 // Initialize TLS session
480 gnutls_init(&Transport::Config
.session
, GNUTLS_CLIENT
);
482 if (Transport::Config
.tlsAnonymous
&& !tryTlsAnonymous()) {
483 gnutls_deinit(Config
.session
);
487 if (!tryTlsCertificate(hostname
)) {
488 gnutls_deinit(Config
.session
);
497 Transport::ShutdownTls()
500 if (!Config
.tlsEnabled
)
503 debugVerbose(3, "Shutting down TLS library...");
505 // release any existing session and credentials
506 gnutls_deinit(Config
.session
);
507 gnutls_anon_free_client_credentials(Config
.anonCredentials
);
508 gnutls_certificate_free_credentials(Config
.certCredentials
);
510 // NP: gnutls init is re-entrant and lock-counted with deinit but not thread safe.
511 gnutls_global_deinit();
512 Config
.tlsEnabled
= false;