]> git.ipfire.org Git - thirdparty/squid.git/blame - tools/squidclient/Transport.cc
SourceFormat Enforcement
[thirdparty/squid.git] / tools / squidclient / Transport.cc
CommitLineData
5f623035 1/*
4ac4a490 2 * Copyright (C) 1996-2017 The Squid Software Foundation and contributors
5f623035
AJ
3 *
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.
7 */
8
b4f805f6
AJ
9#include "squid.h"
10#include "ip/Address.h"
3c65a5d6 11#include "ip/tools.h"
b4f805f6
AJ
12#include "tools/squidclient/Ping.h"
13#include "tools/squidclient/Transport.h"
14
15#if HAVE_GETOPT_H
16#include <getopt.h>
17#endif
18#if HAVE_GNUTLS_X509_H
19#include <gnutls/x509.h>
20#endif
bc1a0bf9 21#include <iostream>
b4f805f6
AJ
22
23Transport::TheConfig Transport::Config;
24
25/// the current server connection FD
26int conn = -1;
27
28void
29Transport::TheConfig::usage()
30{
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
36#if USE_GNUTLS
9958ebee 37 << " --https Use TLS/SSL on the HTTP connection" << std::endl
b4f805f6
AJ
38 << std::endl
39 << " TLS options:" << std::endl
9958ebee 40 << " --anonymous-tls Use Anonymous TLS. Sets default parameters:" << std::endl
b4f805f6
AJ
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
b4f805f6 48#endif
9958ebee 49 << std::endl;
b4f805f6
AJ
50}
51
52bool
53Transport::TheConfig::parseCommandOpts(int argc, char *argv[], int c, int &optIndex)
54{
55 bool tls = false;
ff2b7a42 56 const char *shortOpStr = "h:l:p:T:?";
b4f805f6
AJ
57
58 // options for controlling squidclient transport connection
59 static struct option longOptions[] = {
9958ebee
AJ
60 {"anonymous-tls",no_argument, 0, '\1'},
61 {"https", no_argument, 0, '\3'},
b4f805f6
AJ
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'},
68 {0, 0, 0, 0}
69 };
70
71 int saved_opterr = opterr;
72 opterr = 0; // suppress errors from getopt
73 do {
74 switch (c) {
75 case '\1':
76 tls = true;
77 tlsAnonymous = true;
78 params = "PERFORMANCE:+ANON-ECDH:+ANON-DH";
79 break;
80
81 case '\3':
82 tls = true;
83 break;
84
85 case 'A':
86 tls = true;
87 caFiles.push_back(std::string(optarg));
88 break;
89
90 case 'C':
91 tls = true;
92 certFiles.push_back(std::string(optarg));
93 break;
94
95 case 'h':
96 hostname = optarg;
97 break;
98
99 case 'l':
100 localHost = optarg;
101 break;
102
103 case 'p': /* port number */
104 sscanf(optarg, "%hd", &port);
105 if (port < 1)
106 port = CACHE_HTTP_PORT; /* default */
107 break;
108
109 case 'P':
110 tls = true;
111 params = optarg;
112 break;
113
114 case 'T':
115 ioTimeout = atoi(optarg);
116 break;
117
118 default:
119 if (tls)
120 Transport::InitTls();
121
122 // rewind and let the caller handle unknown options
123 --optind;
124 opterr = saved_opterr;
125 return true;
126 }
127 } while ((c = getopt_long(argc, argv, shortOpStr, longOptions, &optIndex)) != -1);
128
129 if (tls)
130 Transport::InitTls();
131
132 opterr = saved_opterr;
133 return false;
134}
135
136/// Set up the source socket address from which to send.
137static int
138client_comm_bind(int sock, const Ip::Address &addr)
139{
140 static struct addrinfo *AI = NULL;
141 addr.getAddrInfo(AI);
142 int res = bind(sock, AI->ai_addr, AI->ai_addrlen);
851614a8 143 Ip::Address::FreeAddr(AI);
b4f805f6
AJ
144 return res;
145}
146
147static void
148resolveDestination(Ip::Address &iaddr)
149{
150 struct addrinfo *AI = NULL;
151
430b5d15
AJ
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" : ""));
156
b4f805f6
AJ
157 if (Transport::Config.localHost) {
158 debugVerbose(2, "Resolving " << Transport::Config.localHost << " ...");
159
160 if ( !iaddr.GetHostByName(Transport::Config.localHost) ) {
161 std::cerr << "ERROR: Cannot resolve " << Transport::Config.localHost << ": Host unknown." << std::endl;
162 exit(1);
163 }
164 } else {
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;
170 exit(1);
171 }
172 }
173
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;
851614a8 177 Ip::Address::FreeAddr(AI);
b4f805f6
AJ
178 exit(1);
179 }
851614a8 180 Ip::Address::FreeAddr(AI);
b4f805f6
AJ
181
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;
185 exit(1);
186 }
187
188 iaddr.setEmpty();
189
190 debugVerbose(2, "Resolving... " << Transport::Config.hostname);
191
192 if ( !iaddr.GetHostByName(Transport::Config.hostname) ) {
193 std::cerr << "ERROR: Cannot resolve " << Transport::Config.hostname << ": Host unknown." << std::endl;
194 exit(1);
195 }
196 }
197
198 iaddr.port(Transport::Config.port);
199}
200
201/// Set up the destination socket address for message to send to.
202static int
203client_comm_connect(int sock, const Ip::Address &addr)
204{
205 static struct addrinfo *AI = NULL;
206 addr.getAddrInfo(AI);
207 int res = connect(sock, AI->ai_addr, AI->ai_addrlen);
851614a8 208 Ip::Address::FreeAddr(AI);
b4f805f6
AJ
209 Ping::TimerStart();
210 return res;
211}
212
213bool
214Transport::Connect()
215{
216 Ip::Address iaddr;
217 resolveDestination(iaddr);
218
219 debugVerbose(2, "Connecting... " << Config.hostname << " (" << iaddr << ")");
220
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;
226 exit(1);
227 }
228 debugVerbose(2, "Connected to: " << Config.hostname << " (" << iaddr << ")");
229
230 // do any TLS setup that might be needed
231 if (!Transport::MaybeStartTls(Config.hostname))
232 return false;
233
234 return true;
235}
236
237ssize_t
238Transport::Write(void *buf, size_t len)
239{
240 if (conn < 0)
241 return -1;
242
243 if (Config.tlsEnabled) {
244#if USE_GNUTLS
245 gnutls_record_send(Config.session, buf, len);
246 return len;
247#else
248 return 0;
249#endif
250 } else {
251
252#if _SQUID_WINDOWS_
253 return send(conn, buf, len, 0);
254#else
255 alarm(Config.ioTimeout);
256 return write(conn, buf, len);
257#endif
258 }
259}
260
261ssize_t
262Transport::Read(void *buf, size_t len)
263{
264 if (conn < 0)
265 return -1;
266
267 if (Config.tlsEnabled) {
268#if USE_GNUTLS
269 return gnutls_record_recv(Config.session, buf, len);
270#else
271 return 0;
272#endif
273 } else {
274
275#if _SQUID_WINDOWS_
276 return recv(conn, buf, len, 0);
277#else
278 alarm(Config.ioTimeout);
279 return read(conn, buf, len);
280#endif
281 }
282}
283
284void
285Transport::CloseConnection()
286{
287 (void) close(conn);
288 conn = -1;
289}
290
291#if USE_GNUTLS
292/* This function will verify the peer's certificate, and check
293 * if the hostname matches, as well as the activation, expiration dates.
294 */
295static int
296verifyByCA(gnutls_session_t session)
297{
298 /* read hostname */
299 const char *hostname = static_cast<const char*>(gnutls_session_get_ptr(session));
300
301 /* This verification function uses the trusted CAs in the credentials
302 * structure. So you must have installed one or more CA certificates.
303 */
304 unsigned int status;
305 if (gnutls_certificate_verify_peers3(session, hostname, &status) < 0) {
306 std::cerr << "VERIFY peers failure";
307 return GNUTLS_E_CERTIFICATE_ERROR;
308 }
309
310 gnutls_certificate_type_t type = gnutls_certificate_type_get(session);
311 gnutls_datum_t out;
312 if (gnutls_certificate_verification_status_print(status, type, &out, 0) < 0) {
313 std::cerr << "VERIFY status failure";
314 return GNUTLS_E_CERTIFICATE_ERROR;
315 }
316
317 std::cerr << "VERIFY DATUM: " << out.data << std::endl;
318 gnutls_free(out.data);
319
320 if (status != 0) /* Certificate is not trusted */
321 return GNUTLS_E_CERTIFICATE_ERROR;
322
323 /* notify gnutls to continue handshake normally */
324 return GNUTLS_E_SUCCESS;
325}
326
327static int
328verifyTlsCertificate(gnutls_session_t session)
329{
330 // XXX: 1) try to verify using DANE -> Secure Authenticated Connection
331
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;
336 }
337
338 // 3) fails both is insecure, but show the results anyway.
339 std::cerr << "WARNING: Insecure Connection" << std::endl;
340 return GNUTLS_E_SUCCESS;
341}
342#endif
343
344void
345Transport::InitTls()
346{
347#if USE_GNUTLS
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) {
b69e9ffa
AJ
351 int xerrno = errno;
352 std::cerr << "FATAL ERROR: TLS Initialize failed: " << xstrerr(xerrno) << std::endl;
b4f805f6
AJ
353 exit(1);
354 }
355
356 Config.tlsEnabled = true;
357
358 // Initialize for anonymous TLS
359 gnutls_anon_allocate_client_credentials(&Config.anonCredentials);
360
361 // Initialize for X.509 certificate exchange
362 gnutls_certificate_allocate_credentials(&Config.certCredentials);
363 for (std::list<std::string>::const_iterator i = Config.caFiles.begin(); i != Config.caFiles.end(); ++i) {
364 int x = gnutls_certificate_set_x509_trust_file(Config.certCredentials, (*i).c_str(), GNUTLS_X509_FMT_PEM);
365 if (x < 0) {
366 debugVerbose(3, "WARNING: Failed to load Certificate Authorities from " << *i);
367 } else {
368 debugVerbose(3, "Loaded " << x << " Certificate Authorities from " << *i);
369 }
370 }
371 gnutls_certificate_set_verify_function(Config.certCredentials, verifyTlsCertificate);
372
373 for (std::list<std::string>::const_iterator i = Config.certFiles.begin(); i != Config.certFiles.end(); ++i) {
374 if (gnutls_certificate_set_x509_key_file(Transport::Config.certCredentials, (*i).c_str(), (*i).c_str(), GNUTLS_X509_FMT_PEM) != GNUTLS_E_SUCCESS) {
375 debugVerbose(3, "WARNING: Failed to load Certificate from " << *i);
376 } else {
377 debugVerbose(3, "Loaded Certificate from " << *i);
378 }
379 }
380
381#else
382 std::cerr << "ERROR: TLS support not available." << std::endl;
383#endif
384}
385
386#if USE_GNUTLS
387
388// perform the actual handshake exchange with remote server
389static bool
390doTlsHandshake(const char *type)
391{
392 // setup the connection for TLS
393 gnutls_transport_set_int(Transport::Config.session, conn);
394 gnutls_handshake_set_timeout(Transport::Config.session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
395
396 debugVerbose(2, type << " TLS handshake ... ");
397
398 int ret = 0;
399 do {
400 ret = gnutls_handshake(Transport::Config.session);
401 } while (ret < 0 && gnutls_error_is_fatal(ret) == 0);
402
403 if (ret < 0) {
404 std::cerr << "ERROR: " << type << " TLS Handshake failed (" << ret << ") "
405 << gnutls_alert_get_name(gnutls_alert_get(Transport::Config.session))
406 << std::endl;
407 gnutls_perror(ret);
408 gnutls_deinit(Transport::Config.session);
409 return false;
410 }
411
412 char *desc = gnutls_session_get_desc(Transport::Config.session);
413 debugVerbose(3, "TLS Session info: " << std::endl << desc << std::endl);
414 gnutls_free(desc);
415 return true;
416}
417
418static bool
419loadTlsParameters()
420{
421 const char *err = NULL;
422 int x;
423 if ((x = gnutls_priority_set_direct(Transport::Config.session, Transport::Config.params, &err)) != GNUTLS_E_SUCCESS) {
424 if (x == GNUTLS_E_INVALID_REQUEST)
425 std::cerr << "ERROR: Syntax error at: " << err << std::endl;
426 gnutls_perror(x);
427 return false;
428 }
429 return true;
430}
431
432// attempt an anonymous TLS handshake
433// this encrypts the connection but does not secure it
434// so many public servers do not support this handshake type.
435static bool
436tryTlsAnonymous()
437{
438 if (!loadTlsParameters())
439 return false;
440
441 // put the anonymous credentials to the current session
442 int x;
443 if ((x = gnutls_credentials_set(Transport::Config.session, GNUTLS_CRD_ANON, Transport::Config.anonCredentials)) != GNUTLS_E_SUCCESS) {
444 std::cerr << "ERROR: Anonymous TLS credentials setup failed (" << x << ") " << std::endl;
445 gnutls_perror(x);
446 return false;
447 }
448
449 return doTlsHandshake("Anonymous");
450}
451
452// attempt a X.509 certificate exchange
453// this both encrypts and authenticates the connection
454static bool
455tryTlsCertificate(const char *hostname)
456{
457 gnutls_session_set_ptr(Transport::Config.session, (void *) hostname);
458 gnutls_server_name_set(Transport::Config.session, GNUTLS_NAME_DNS, hostname, strlen(hostname));
459
460 if (!loadTlsParameters())
461 return false;
462
463 // put the X.509 credentials to the current session
464 gnutls_credentials_set(Transport::Config.session, GNUTLS_CRD_CERTIFICATE, Transport::Config.certCredentials);
465
466 // setup the connection for TLS
467 gnutls_transport_set_int(Transport::Config.session, conn);
468 gnutls_handshake_set_timeout(Transport::Config.session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
469
470 return doTlsHandshake("X.509");
471}
472#endif
473
474bool
475Transport::MaybeStartTls(const char *hostname)
476{
477#if USE_GNUTLS
478 if (Config.tlsEnabled) {
479
480 // Initialize TLS session
481 gnutls_init(&Transport::Config.session, GNUTLS_CLIENT);
482
483 if (Transport::Config.tlsAnonymous && !tryTlsAnonymous()) {
484 gnutls_deinit(Config.session);
485 return false;
486 }
487
488 if (!tryTlsCertificate(hostname)) {
489 gnutls_deinit(Config.session);
490 return false;
491 }
492 }
493#endif
494 return true;
495}
496
497void
498Transport::ShutdownTls()
499{
500#if USE_GNUTLS
501 if (!Config.tlsEnabled)
502 return;
503
504 debugVerbose(3, "Shutting down TLS library...");
505
506 // release any existing session and credentials
507 gnutls_deinit(Config.session);
508 gnutls_anon_free_client_credentials(Config.anonCredentials);
509 gnutls_certificate_free_credentials(Config.certCredentials);
510
511 // NP: gnutls init is re-entrant and lock-counted with deinit but not thread safe.
512 gnutls_global_deinit();
513 Config.tlsEnabled = false;
514#endif
515}
f53969cc 516