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