]>
Commit | Line | Data |
---|---|---|
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 | |
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 | |
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 | ||
52 | bool | |
53 | Transport::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. | |
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); | |
851614a8 | 143 | Ip::Address::FreeAddr(AI); |
b4f805f6 AJ |
144 | return res; |
145 | } | |
146 | ||
147 | static void | |
148 | resolveDestination(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. | |
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); | |
851614a8 | 208 | Ip::Address::FreeAddr(AI); |
b4f805f6 AJ |
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) { | |
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 | |
389 | static bool | |
390 | doTlsHandshake(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 | ||
418 | static bool | |
419 | loadTlsParameters() | |
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. | |
435 | static bool | |
436 | tryTlsAnonymous() | |
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 | |
454 | static bool | |
455 | tryTlsCertificate(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 | ||
474 | bool | |
475 | Transport::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 | ||
497 | void | |
498 | Transport::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 |