]> git.ipfire.org Git - thirdparty/squid.git/blob - tools/squidclient/Transport.cc
SourceFormat Enforcement
[thirdparty/squid.git] / tools / squidclient / Transport.cc
1 /*
2 * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
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
9 #include "squid.h"
10 #include "ip/Address.h"
11 #include "ip/tools.h"
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
21 #include <iostream>
22
23 Transport::TheConfig Transport::Config;
24
25 /// the current server connection FD
26 int conn = -1;
27
28 void
29 Transport::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
37 << " --https Use TLS/SSL on the HTTP connection" << std::endl
38 << 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
48 #endif
49 << std::endl;
50 }
51
52 bool
53 Transport::TheConfig::parseCommandOpts(int argc, char *argv[], int c, int &optIndex)
54 {
55 bool tls = false;
56 const char *shortOpStr = "h:l:p:T:?";
57
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'},
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.
137 static int
138 client_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);
143 Ip::Address::FreeAddr(AI);
144 return res;
145 }
146
147 static void
148 resolveDestination(Ip::Address &iaddr)
149 {
150 struct addrinfo *AI = NULL;
151
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
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;
177 Ip::Address::FreeAddr(AI);
178 exit(1);
179 }
180 Ip::Address::FreeAddr(AI);
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.
202 static int
203 client_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);
208 Ip::Address::FreeAddr(AI);
209 Ping::TimerStart();
210 return res;
211 }
212
213 bool
214 Transport::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
237 ssize_t
238 Transport::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
261 ssize_t
262 Transport::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
284 void
285 Transport::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 */
295 static int
296 verifyByCA(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
327 static int
328 verifyTlsCertificate(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
344 void
345 Transport::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) {
351 std::cerr << "FATAL ERROR: TLS Initialize failed: " << xstrerror() << std::endl;
352 exit(1);
353 }
354
355 Config.tlsEnabled = true;
356
357 // Initialize for anonymous TLS
358 gnutls_anon_allocate_client_credentials(&Config.anonCredentials);
359
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);
364 if (x < 0) {
365 debugVerbose(3, "WARNING: Failed to load Certificate Authorities from " << *i);
366 } else {
367 debugVerbose(3, "Loaded " << x << " Certificate Authorities from " << *i);
368 }
369 }
370 gnutls_certificate_set_verify_function(Config.certCredentials, verifyTlsCertificate);
371
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);
375 } else {
376 debugVerbose(3, "Loaded Certificate from " << *i);
377 }
378 }
379
380 #else
381 std::cerr << "ERROR: TLS support not available." << std::endl;
382 #endif
383 }
384
385 #if USE_GNUTLS
386
387 // perform the actual handshake exchange with remote server
388 static bool
389 doTlsHandshake(const char *type)
390 {
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);
394
395 debugVerbose(2, type << " TLS handshake ... ");
396
397 int ret = 0;
398 do {
399 ret = gnutls_handshake(Transport::Config.session);
400 } while (ret < 0 && gnutls_error_is_fatal(ret) == 0);
401
402 if (ret < 0) {
403 std::cerr << "ERROR: " << type << " TLS Handshake failed (" << ret << ") "
404 << gnutls_alert_get_name(gnutls_alert_get(Transport::Config.session))
405 << std::endl;
406 gnutls_perror(ret);
407 gnutls_deinit(Transport::Config.session);
408 return false;
409 }
410
411 char *desc = gnutls_session_get_desc(Transport::Config.session);
412 debugVerbose(3, "TLS Session info: " << std::endl << desc << std::endl);
413 gnutls_free(desc);
414 return true;
415 }
416
417 static bool
418 loadTlsParameters()
419 {
420 const char *err = NULL;
421 int x;
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;
425 gnutls_perror(x);
426 return false;
427 }
428 return true;
429 }
430
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.
434 static bool
435 tryTlsAnonymous()
436 {
437 if (!loadTlsParameters())
438 return false;
439
440 // put the anonymous credentials to the current session
441 int x;
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;
444 gnutls_perror(x);
445 return false;
446 }
447
448 return doTlsHandshake("Anonymous");
449 }
450
451 // attempt a X.509 certificate exchange
452 // this both encrypts and authenticates the connection
453 static bool
454 tryTlsCertificate(const char *hostname)
455 {
456 gnutls_session_set_ptr(Transport::Config.session, (void *) hostname);
457 gnutls_server_name_set(Transport::Config.session, GNUTLS_NAME_DNS, hostname, strlen(hostname));
458
459 if (!loadTlsParameters())
460 return false;
461
462 // put the X.509 credentials to the current session
463 gnutls_credentials_set(Transport::Config.session, GNUTLS_CRD_CERTIFICATE, Transport::Config.certCredentials);
464
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);
468
469 return doTlsHandshake("X.509");
470 }
471 #endif
472
473 bool
474 Transport::MaybeStartTls(const char *hostname)
475 {
476 #if USE_GNUTLS
477 if (Config.tlsEnabled) {
478
479 // Initialize TLS session
480 gnutls_init(&Transport::Config.session, GNUTLS_CLIENT);
481
482 if (Transport::Config.tlsAnonymous && !tryTlsAnonymous()) {
483 gnutls_deinit(Config.session);
484 return false;
485 }
486
487 if (!tryTlsCertificate(hostname)) {
488 gnutls_deinit(Config.session);
489 return false;
490 }
491 }
492 #endif
493 return true;
494 }
495
496 void
497 Transport::ShutdownTls()
498 {
499 #if USE_GNUTLS
500 if (!Config.tlsEnabled)
501 return;
502
503 debugVerbose(3, "Shutting down TLS library...");
504
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);
509
510 // NP: gnutls init is re-entrant and lock-counted with deinit but not thread safe.
511 gnutls_global_deinit();
512 Config.tlsEnabled = false;
513 #endif
514 }
515