2 * Copyright (C) 1996-2014 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"
11 #include "tools/squidclient/Ping.h"
12 #include "tools/squidclient/Transport.h"
17 #if HAVE_GNUTLS_X509_H
18 #include <gnutls/x509.h>
22 Transport::TheConfig
Transport::Config
;
24 /// the current server connection FD
28 Transport::TheConfig::usage()
30 std::cerr
<< "Connection Settings" << std::endl
31 << " -h | --host host Send message to server on 'host'. Default is localhost." << std::endl
32 << " -l | --local host Specify a local IP address to bind to. Default is none." << std::endl
33 << " -p | --port port Port number on server to contact. Default is " << CACHE_HTTP_PORT
<< "." << std::endl
34 << " -T timeout Timeout in seconds for read/write operations" << std::endl
36 << " --https Use TLS/SSL on the HTTP connection" << std::endl
38 << " TLS options:" << std::endl
39 << " --anonymous-tls Use Anonymous TLS. Sets default parameters:" << std::endl
40 << " \"PERFORMANCE:+ANON-ECDH:+ANON-DH\"" << std::endl
41 << " --params=\"...\" Use the given parameters." << std::endl
42 << " --cert=FILE Path to a PEM file holding the client X.509 certificate chain." << std::endl
43 << " May be repeated if there are multiple certificates to use for the server." << std::endl
44 << " --trusted-ca=PATH Path to a PEM file holding trusted CA certificate(s)." << std::endl
45 << " May be repeated." << std::endl
46 << " Example path: \"/etc/ssl/certs/ca-certificates.crt\"" << std::endl
52 Transport::TheConfig::parseCommandOpts(int argc
, char *argv
[], int c
, int &optIndex
)
55 const char *shortOpStr
= "A:C:h:l:p:P:T:?";
57 // options for controlling squidclient transport connection
58 static struct option longOptions
[] = {
59 {"anonymous-tls",no_argument
, 0, '\1'},
60 {"https", no_argument
, 0, '\3'},
61 {"trusted-ca", required_argument
, 0, 'A'},
62 {"cert", required_argument
, 0, 'C'},
63 {"host", required_argument
, 0, 'h'},
64 {"local", required_argument
, 0, 'l'},
65 {"port", required_argument
, 0, 'p'},
66 {"params", required_argument
, 0, 'P'},
70 int saved_opterr
= opterr
;
71 opterr
= 0; // suppress errors from getopt
77 params
= "PERFORMANCE:+ANON-ECDH:+ANON-DH";
86 caFiles
.push_back(std::string(optarg
));
91 certFiles
.push_back(std::string(optarg
));
102 case 'p': /* port number */
103 sscanf(optarg
, "%hd", &port
);
105 port
= CACHE_HTTP_PORT
; /* default */
114 ioTimeout
= atoi(optarg
);
119 Transport::InitTls();
121 // rewind and let the caller handle unknown options
123 opterr
= saved_opterr
;
126 } while ((c
= getopt_long(argc
, argv
, shortOpStr
, longOptions
, &optIndex
)) != -1);
129 Transport::InitTls();
131 opterr
= saved_opterr
;
135 /// Set up the source socket address from which to send.
137 client_comm_bind(int sock
, const Ip::Address
&addr
)
139 static struct addrinfo
*AI
= NULL
;
140 addr
.getAddrInfo(AI
);
141 int res
= bind(sock
, AI
->ai_addr
, AI
->ai_addrlen
);
142 Ip::Address::FreeAddr(AI
);
147 resolveDestination(Ip::Address
&iaddr
)
149 struct addrinfo
*AI
= NULL
;
151 if (Transport::Config
.localHost
) {
152 debugVerbose(2, "Resolving " << Transport::Config
.localHost
<< " ...");
154 if ( !iaddr
.GetHostByName(Transport::Config
.localHost
) ) {
155 std::cerr
<< "ERROR: Cannot resolve " << Transport::Config
.localHost
<< ": Host unknown." << std::endl
;
159 debugVerbose(2, "Resolving " << Transport::Config
.hostname
<< " ...");
160 /* Process the remote host name to locate the Protocol required
161 in case we are being asked to link to another version of squid */
162 if ( !iaddr
.GetHostByName(Transport::Config
.hostname
) ) {
163 std::cerr
<< "ERROR: Cannot resolve " << Transport::Config
.hostname
<< ": Host unknown." << std::endl
;
168 iaddr
.getAddrInfo(AI
);
169 if ((conn
= socket(AI
->ai_family
, AI
->ai_socktype
, 0)) < 0) {
170 std::cerr
<< "ERROR: could not open socket to " << iaddr
<< std::endl
;
171 Ip::Address::FreeAddr(AI
);
174 Ip::Address::FreeAddr(AI
);
176 if (Transport::Config
.localHost
) {
177 if (client_comm_bind(conn
, iaddr
) < 0) {
178 std::cerr
<< "ERROR: could not bind socket to " << iaddr
<< std::endl
;
184 debugVerbose(2, "Resolving... " << Transport::Config
.hostname
);
186 if ( !iaddr
.GetHostByName(Transport::Config
.hostname
) ) {
187 std::cerr
<< "ERROR: Cannot resolve " << Transport::Config
.hostname
<< ": Host unknown." << std::endl
;
192 iaddr
.port(Transport::Config
.port
);
195 /// Set up the destination socket address for message to send to.
197 client_comm_connect(int sock
, const Ip::Address
&addr
)
199 static struct addrinfo
*AI
= NULL
;
200 addr
.getAddrInfo(AI
);
201 int res
= connect(sock
, AI
->ai_addr
, AI
->ai_addrlen
);
202 Ip::Address::FreeAddr(AI
);
211 resolveDestination(iaddr
);
213 debugVerbose(2, "Connecting... " << Config
.hostname
<< " (" << iaddr
<< ")");
215 if (client_comm_connect(conn
, iaddr
) < 0) {
216 char hostnameBuf
[MAX_IPSTRLEN
];
217 iaddr
.toUrl(hostnameBuf
, MAX_IPSTRLEN
);
218 std::cerr
<< "ERROR: Cannot connect to " << hostnameBuf
219 << (!errno
?": Host unknown." : "") << std::endl
;
222 debugVerbose(2, "Connected to: " << Config
.hostname
<< " (" << iaddr
<< ")");
224 // do any TLS setup that might be needed
225 if (!Transport::MaybeStartTls(Config
.hostname
))
232 Transport::Write(void *buf
, size_t len
)
237 if (Config
.tlsEnabled
) {
239 gnutls_record_send(Config
.session
, buf
, len
);
247 return send(conn
, buf
, len
, 0);
249 alarm(Config
.ioTimeout
);
250 return write(conn
, buf
, len
);
256 Transport::Read(void *buf
, size_t len
)
261 if (Config
.tlsEnabled
) {
263 return gnutls_record_recv(Config
.session
, buf
, len
);
270 return recv(conn
, buf
, len
, 0);
272 alarm(Config
.ioTimeout
);
273 return read(conn
, buf
, len
);
279 Transport::CloseConnection()
286 /* This function will verify the peer's certificate, and check
287 * if the hostname matches, as well as the activation, expiration dates.
290 verifyByCA(gnutls_session_t session
)
293 const char *hostname
= static_cast<const char*>(gnutls_session_get_ptr(session
));
295 /* This verification function uses the trusted CAs in the credentials
296 * structure. So you must have installed one or more CA certificates.
299 if (gnutls_certificate_verify_peers3(session
, hostname
, &status
) < 0) {
300 std::cerr
<< "VERIFY peers failure";
301 return GNUTLS_E_CERTIFICATE_ERROR
;
304 gnutls_certificate_type_t type
= gnutls_certificate_type_get(session
);
306 if (gnutls_certificate_verification_status_print(status
, type
, &out
, 0) < 0) {
307 std::cerr
<< "VERIFY status failure";
308 return GNUTLS_E_CERTIFICATE_ERROR
;
311 std::cerr
<< "VERIFY DATUM: " << out
.data
<< std::endl
;
312 gnutls_free(out
.data
);
314 if (status
!= 0) /* Certificate is not trusted */
315 return GNUTLS_E_CERTIFICATE_ERROR
;
317 /* notify gnutls to continue handshake normally */
318 return GNUTLS_E_SUCCESS
;
322 verifyTlsCertificate(gnutls_session_t session
)
324 // XXX: 1) try to verify using DANE -> Secure Authenticated Connection
326 // 2) try to verify using CA
327 if (verifyByCA(session
) == GNUTLS_E_SUCCESS
) {
328 std::cerr
<< "SUCCESS: CA verified Encrypted Connection" << std::endl
;
329 return GNUTLS_E_SUCCESS
;
332 // 3) fails both is insecure, but show the results anyway.
333 std::cerr
<< "WARNING: Insecure Connection" << std::endl
;
334 return GNUTLS_E_SUCCESS
;
342 debugVerbose(3, "Initializing TLS library...");
343 // NP: gnutls init is re-entrant and lock-counted with deinit but not thread safe.
344 if (gnutls_global_init() != GNUTLS_E_SUCCESS
) {
345 std::cerr
<< "FATAL ERROR: TLS Initialize failed: " << xstrerror() << std::endl
;
349 Config
.tlsEnabled
= true;
351 // Initialize for anonymous TLS
352 gnutls_anon_allocate_client_credentials(&Config
.anonCredentials
);
354 // Initialize for X.509 certificate exchange
355 gnutls_certificate_allocate_credentials(&Config
.certCredentials
);
356 for (std::list
<std::string
>::const_iterator i
= Config
.caFiles
.begin(); i
!= Config
.caFiles
.end(); ++i
) {
357 int x
= gnutls_certificate_set_x509_trust_file(Config
.certCredentials
, (*i
).c_str(), GNUTLS_X509_FMT_PEM
);
359 debugVerbose(3, "WARNING: Failed to load Certificate Authorities from " << *i
);
361 debugVerbose(3, "Loaded " << x
<< " Certificate Authorities from " << *i
);
364 gnutls_certificate_set_verify_function(Config
.certCredentials
, verifyTlsCertificate
);
366 for (std::list
<std::string
>::const_iterator i
= Config
.certFiles
.begin(); i
!= Config
.certFiles
.end(); ++i
) {
367 if (gnutls_certificate_set_x509_key_file(Transport::Config
.certCredentials
, (*i
).c_str(), (*i
).c_str(), GNUTLS_X509_FMT_PEM
) != GNUTLS_E_SUCCESS
) {
368 debugVerbose(3, "WARNING: Failed to load Certificate from " << *i
);
370 debugVerbose(3, "Loaded Certificate from " << *i
);
375 std::cerr
<< "ERROR: TLS support not available." << std::endl
;
381 // perform the actual handshake exchange with remote server
383 doTlsHandshake(const char *type
)
385 // setup the connection for TLS
386 gnutls_transport_set_int(Transport::Config
.session
, conn
);
387 gnutls_handshake_set_timeout(Transport::Config
.session
, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT
);
389 debugVerbose(2, type
<< " TLS handshake ... ");
393 ret
= gnutls_handshake(Transport::Config
.session
);
394 } while (ret
< 0 && gnutls_error_is_fatal(ret
) == 0);
397 std::cerr
<< "ERROR: " << type
<< " TLS Handshake failed (" << ret
<< ") "
398 << gnutls_alert_get_name(gnutls_alert_get(Transport::Config
.session
))
401 gnutls_deinit(Transport::Config
.session
);
405 char *desc
= gnutls_session_get_desc(Transport::Config
.session
);
406 debugVerbose(3, "TLS Session info: " << std::endl
<< desc
<< std::endl
);
414 const char *err
= NULL
;
416 if ((x
= gnutls_priority_set_direct(Transport::Config
.session
, Transport::Config
.params
, &err
)) != GNUTLS_E_SUCCESS
) {
417 if (x
== GNUTLS_E_INVALID_REQUEST
)
418 std::cerr
<< "ERROR: Syntax error at: " << err
<< std::endl
;
425 // attempt an anonymous TLS handshake
426 // this encrypts the connection but does not secure it
427 // so many public servers do not support this handshake type.
431 if (!loadTlsParameters())
434 // put the anonymous credentials to the current session
436 if ((x
= gnutls_credentials_set(Transport::Config
.session
, GNUTLS_CRD_ANON
, Transport::Config
.anonCredentials
)) != GNUTLS_E_SUCCESS
) {
437 std::cerr
<< "ERROR: Anonymous TLS credentials setup failed (" << x
<< ") " << std::endl
;
442 return doTlsHandshake("Anonymous");
445 // attempt a X.509 certificate exchange
446 // this both encrypts and authenticates the connection
448 tryTlsCertificate(const char *hostname
)
450 gnutls_session_set_ptr(Transport::Config
.session
, (void *) hostname
);
451 gnutls_server_name_set(Transport::Config
.session
, GNUTLS_NAME_DNS
, hostname
, strlen(hostname
));
453 if (!loadTlsParameters())
456 // put the X.509 credentials to the current session
457 gnutls_credentials_set(Transport::Config
.session
, GNUTLS_CRD_CERTIFICATE
, Transport::Config
.certCredentials
);
459 // setup the connection for TLS
460 gnutls_transport_set_int(Transport::Config
.session
, conn
);
461 gnutls_handshake_set_timeout(Transport::Config
.session
, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT
);
463 return doTlsHandshake("X.509");
468 Transport::MaybeStartTls(const char *hostname
)
471 if (Config
.tlsEnabled
) {
473 // Initialize TLS session
474 gnutls_init(&Transport::Config
.session
, GNUTLS_CLIENT
);
476 if (Transport::Config
.tlsAnonymous
&& !tryTlsAnonymous()) {
477 gnutls_deinit(Config
.session
);
481 if (!tryTlsCertificate(hostname
)) {
482 gnutls_deinit(Config
.session
);
491 Transport::ShutdownTls()
494 if (!Config
.tlsEnabled
)
497 debugVerbose(3, "Shutting down TLS library...");
499 // release any existing session and credentials
500 gnutls_deinit(Config
.session
);
501 gnutls_anon_free_client_credentials(Config
.anonCredentials
);
502 gnutls_certificate_free_credentials(Config
.certCredentials
);
504 // NP: gnutls init is re-entrant and lock-counted with deinit but not thread safe.
505 gnutls_global_deinit();
506 Config
.tlsEnabled
= false;