]> git.ipfire.org Git - thirdparty/squid.git/blob - tools/squidclient/squidclient.cc
SourceFormat Enforcement
[thirdparty/squid.git] / tools / squidclient / squidclient.cc
1 /*
2 * Copyright (C) 1996-2017 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 "base64.h"
11 #include "ip/Address.h"
12 #include "ip/tools.h"
13 #include "rfc1123.h"
14 #include "tools/squidclient/gssapi_support.h"
15 #include "tools/squidclient/Parameters.h"
16 #include "tools/squidclient/Ping.h"
17 #include "tools/squidclient/Transport.h"
18
19 #if _SQUID_WINDOWS_
20 /** \cond AUTODOCS-IGNORE */
21 using namespace Squid;
22 /** \endcond */
23 #endif
24
25 #include <cerrno>
26 #include <csignal>
27 #include <cstring>
28 #include <iostream>
29 #if _SQUID_WINDOWS_
30 #include <io.h>
31 #endif
32 #if HAVE_SYS_SOCKET_H
33 #include <sys/socket.h>
34 #endif
35 #if HAVE_UNISTD_H
36 #include <unistd.h>
37 #endif
38 #if HAVE_NETDB_H
39 #include <netdb.h>
40 #endif
41 #if HAVE_SYS_STAT_H
42 #include <sys/stat.h>
43 #endif
44 #if HAVE_FCNTL_H
45 #include <fcntl.h>
46 #endif
47 #if HAVE_NETINET_IN_H
48 #include <netinet/in.h>
49 #endif
50 #if HAVE_GETOPT_H
51 #include <getopt.h>
52 #endif
53
54 #ifndef BUFSIZ
55 #define BUFSIZ 8192
56 #endif
57 #ifndef MESSAGELEN
58 #define MESSAGELEN 65536
59 #endif
60 #ifndef HEADERLEN
61 #define HEADERLEN 65536
62 #endif
63
64 /* Local functions */
65 static void usage(const char *progname);
66
67 void pipe_handler(int sig);
68 static void set_our_signal(void);
69
70 Parameters scParams;
71
72 static int put_fd;
73 static char *put_file = NULL;
74
75 static struct stat sb;
76 int total_bytes = 0;
77
78 #if _SQUID_AIX_
79 /* Bug 3854: AIX 6.1 tries to link in this fde.h global symbol
80 * despite squidclient not using any of the fd_* code.
81 */
82 fde *fde::Table = NULL;
83 #endif
84
85 #if _SQUID_WINDOWS_
86 void
87 Win32SockCleanup(void)
88 {
89 WSACleanup();
90 return;
91 }
92 #endif
93
94 static void
95 usage(const char *progname)
96 {
97 std::cerr << "Version: " << VERSION << std::endl
98 << "Usage: " << progname << " [Basic Options] [HTTP Options]" << std::endl
99 << std::endl;
100 std::cerr
101 << " -s | --quiet Silent. Do not print response message to stdout." << std::endl
102 << " -v | --verbose Verbose debugging. Repeat (-vv) to increase output level." << std::endl
103 << " Levels:" << std::endl
104 << " 1 - Print outgoing request message to stderr." << std::endl
105 << " 2 - Print action trace to stderr." << std::endl
106 << " --help Display this help text." << std::endl
107 << std::endl;
108 Transport::Config.usage();
109 Ping::Config.usage();
110 std::cerr
111 << "HTTP Options:" << std::endl
112 << " -a Do NOT include Accept: header." << std::endl
113 << " -A User-Agent: header. Use \"\" to omit." << std::endl
114 << " -H 'string' Extra headers to send. Supports '\\\\', '\\n', '\\r' and '\\t'." << std::endl
115 << " -i IMS If-Modified-Since time (in Epoch seconds)." << std::endl
116 << " -j hosthdr Host header content" << std::endl
117 << " -k Keep the connection active. Default is to do only one request then close." << std::endl
118 << " -m method Request method, default is GET." << std::endl
119 #if HAVE_GSSAPI
120 << " -n Proxy Negotiate(Kerberos) authentication" << std::endl
121 << " -N WWW Negotiate(Kerberos) authentication" << std::endl
122 #endif
123 << " -P file Send content from the named file as request payload" << std::endl
124 << " -r Force cache to reload URL" << std::endl
125 << " -t count Trace count cache-hops" << std::endl
126 << " -u user Proxy authentication username" << std::endl
127 << " -U user WWW authentication username" << std::endl
128 << " -V version HTTP Version. Use '-' for HTTP/0.9 omitted case" << std::endl
129 << " -w password Proxy authentication password" << std::endl
130 << " -W password WWW authentication password" << std::endl
131 ;
132 exit(1);
133 }
134
135 static void
136 shellUnescape(char *buf)
137 {
138 if (!buf)
139 return;
140
141 unsigned char *p, *d;
142
143 d = p = reinterpret_cast<unsigned char *>(buf);
144
145 while (auto ch = *p) {
146
147 if (ch == '\\') {
148 ++p;
149
150 switch (*p) {
151 case 'n':
152 ch = '\n';
153 break;
154 case 'r':
155 ch = '\r';
156 break;
157 case 't':
158 ch = '\t';
159 break;
160 case '\\':
161 ch = '\\';
162 break;
163 default:
164 ch = *p;
165 debugVerbose(1, "Warning: unsupported shell code '\\" << ch << "'");
166 break;
167 }
168
169 *d = ch;
170
171 if (!ch)
172 continue;
173
174 } else {
175 *d = *p;
176 }
177
178 ++p;
179 ++d;
180 }
181
182 *d = '\0';
183 }
184
185 int
186 main(int argc, char *argv[])
187 {
188 int len, bytesWritten;
189 bool to_stdout, reload;
190 int keep_alive = 0;
191 int opt_noaccept = 0;
192 #if HAVE_GSSAPI
193 int www_neg = 0, proxy_neg = 0;
194 #endif
195 char url[BUFSIZ], msg[MESSAGELEN], buf[BUFSIZ];
196 char extra_hdrs[HEADERLEN];
197 const char *method = "GET";
198 extern char *optarg;
199 time_t ims = 0;
200 int max_forwards = -1;
201
202 const char *proxy_user = NULL;
203 const char *proxy_password = NULL;
204 const char *www_user = NULL;
205 const char *www_password = NULL;
206 const char *host = NULL;
207 const char *version = "1.0";
208 const char *useragent = NULL;
209
210 /* set the defaults */
211 extra_hdrs[0] = '\0';
212 to_stdout = true;
213 reload = false;
214
215 Ip::ProbeTransport(); // determine IPv4 or IPv6 capabilities before parsing.
216 if (argc < 2 || argv[argc-1][0] == '-') {
217 usage(argv[0]); /* need URL */
218 } else if (argc >= 2) {
219 strncpy(url, argv[argc - 1], BUFSIZ);
220 url[BUFSIZ - 1] = '\0';
221
222 int optIndex = 0;
223 const char *shortOpStr = "aA:h:j:V:l:P:i:km:nNp:rsvt:H:T:u:U:w:W:?";
224
225 // options for controlling squidclient
226 static struct option basicOptions[] = {
227 /* These are the generic options for squidclient itself */
228 {"help", no_argument, 0, '?'},
229 {"verbose", no_argument, 0, 'v'},
230 {"quiet", no_argument, 0, 's'},
231 {"host", required_argument, 0, 'h'},
232 {"local", required_argument, 0, 'l'},
233 {"port", required_argument, 0, 'p'},
234 {"ping", no_argument, 0, '\1'},
235 {"https", no_argument, 0, '\3'},
236 {0, 0, 0, 0}
237 };
238
239 int c;
240 while ((c = getopt_long(argc, argv, shortOpStr, basicOptions, &optIndex)) != -1) {
241
242 // modules parse their own specific options
243 switch (c) {
244 case '\1':
245 to_stdout = 0;
246 Ping::Config.parseCommandOpts(argc, argv, c, optIndex);
247 continue;
248
249 case 'h': /* remote host */
250 case 'l': /* local host */
251 case 'p': /* port number */
252 // rewind and let the Transport::Config parser handle
253 optind -= 2;
254
255 case '\3': // request over a TLS connection
256 Transport::Config.parseCommandOpts(argc, argv, c, optIndex);
257 continue;
258
259 default: // fall through to next switch
260 break;
261 }
262
263 switch (c) {
264
265 case '\0': // dummy value for end-of-options
266 break;
267
268 case 'a':
269 opt_noaccept = 1;
270 break;
271
272 case 'A':
273 useragent = optarg;
274 break;
275
276 case 'j':
277 host = optarg;
278 break;
279
280 case 'V':
281 version = optarg;
282 break;
283
284 case 's': /* silent */
285 to_stdout = false;
286 break;
287
288 case 'k': /* backward compat */
289 keep_alive = 1;
290 break;
291
292 case 'r': /* reload */
293 reload = true;
294 break;
295
296 case 'P':
297 put_file = xstrdup(optarg);
298 break;
299
300 case 'i': /* IMS */
301 ims = (time_t) atoi(optarg);
302 break;
303
304 case 'm':
305 method = xstrdup(optarg);
306 break;
307
308 case 't':
309 method = xstrdup("TRACE");
310 max_forwards = atoi(optarg);
311 break;
312
313 case 'H':
314 if (strlen(optarg)) {
315 strncpy(extra_hdrs, optarg, sizeof(extra_hdrs));
316 shellUnescape(extra_hdrs);
317 }
318 break;
319
320 case 'T':
321 Transport::Config.ioTimeout = atoi(optarg);
322 break;
323
324 case 'u':
325 proxy_user = optarg;
326 break;
327
328 case 'w':
329 proxy_password = optarg;
330 break;
331
332 case 'U':
333 www_user = optarg;
334 break;
335
336 case 'W':
337 www_password = optarg;
338 break;
339
340 case 'n':
341 #if HAVE_GSSAPI
342 proxy_neg = 1;
343 #else
344 std::cerr << "ERROR: Negotiate authentication not supported." << std::endl;
345 usage(argv[0]);
346 #endif
347 break;
348
349 case 'N':
350 #if HAVE_GSSAPI
351 www_neg = 1;
352 #else
353 std::cerr << "ERROR: Negotiate authentication not supported." << std::endl;
354 usage(argv[0]);
355 #endif
356 break;
357
358 case 'v':
359 /* undocumented: may increase verb-level by giving more -v's */
360 ++scParams.verbosityLevel;
361 debugVerbose(2, "verbosity level set to " << scParams.verbosityLevel);
362 break;
363
364 case '?': /* usage */
365
366 default:
367 usage(argv[0]);
368 break;
369 }
370 }
371 }
372 #if _SQUID_WINDOWS_
373 {
374 WSADATA wsaData;
375 WSAStartup(2, &wsaData);
376 atexit(Win32SockCleanup);
377 }
378 #endif
379 /* Build the HTTP request */
380 if (strncmp(url, "mgr:", 4) == 0) {
381 char *t = xstrdup(url + 4);
382 const char *at = NULL;
383 if (!strrchr(t, '@')) { // ignore any -w password if @ is explicit already.
384 at = proxy_password;
385 }
386 // embed the -w proxy password into old-style cachemgr URLs
387 if (at)
388 snprintf(url, BUFSIZ, "cache_object://%s/%s@%s", Transport::Config.hostname, t, at);
389 else
390 snprintf(url, BUFSIZ, "cache_object://%s/%s", Transport::Config.hostname, t);
391 xfree(t);
392 }
393 if (put_file) {
394 put_fd = open(put_file, O_RDONLY);
395 set_our_signal();
396
397 if (put_fd < 0) {
398 int xerrno = errno;
399 std::cerr << "ERROR: can't open file (" << xstrerr(xerrno) << ")" << std::endl;
400 exit(-1);
401 }
402 #if _SQUID_WINDOWS_
403 setmode(put_fd, O_BINARY);
404 #endif
405
406 if (fstat(put_fd, &sb) < 0) {
407 int xerrno = errno;
408 std::cerr << "ERROR: can't identify length of file (" << xstrerr(xerrno) << ")" << std::endl;
409 }
410 }
411
412 if (!host) {
413 char *newhost = strstr(url, "://");
414 if (newhost) {
415 char *t;
416 newhost += 3;
417 newhost = xstrdup(newhost);
418 t = newhost + strcspn(newhost, "@/?");
419 if (*t == '@') {
420 newhost = t + 1;
421 t = newhost + strcspn(newhost, "@/?");
422 }
423 *t = '\0';
424 host = newhost;
425 }
426 }
427
428 if (version[0] == '-' || !version[0]) {
429 /* HTTP/0.9, no headers, no version */
430 snprintf(msg, BUFSIZ, "%s %s\r\n", method, url);
431 } else {
432 if (!xisdigit(version[0])) // not HTTP/n.n
433 snprintf(msg, BUFSIZ, "%s %s %s\r\n", method, url, version);
434 else
435 snprintf(msg, BUFSIZ, "%s %s HTTP/%s\r\n", method, url, version);
436
437 if (host) {
438 snprintf(buf, BUFSIZ, "Host: %s\r\n", host);
439 strcat(msg,buf);
440 }
441
442 if (useragent == NULL) {
443 snprintf(buf, BUFSIZ, "User-Agent: squidclient/%s\r\n", VERSION);
444 strcat(msg,buf);
445 } else if (useragent[0] != '\0') {
446 snprintf(buf, BUFSIZ, "User-Agent: %s\r\n", useragent);
447 strcat(msg,buf);
448 }
449
450 if (reload) {
451 snprintf(buf, BUFSIZ, "Cache-Control: no-cache\r\n");
452 strcat(msg, buf);
453 }
454 if (put_fd > 0) {
455 snprintf(buf, BUFSIZ, "Content-length: %" PRId64 "\r\n", (int64_t) sb.st_size);
456 strcat(msg, buf);
457 }
458 if (opt_noaccept == 0) {
459 snprintf(buf, BUFSIZ, "Accept: */*\r\n");
460 strcat(msg, buf);
461 }
462 if (ims) {
463 snprintf(buf, BUFSIZ, "If-Modified-Since: %s\r\n", mkrfc1123(ims));
464 strcat(msg, buf);
465 }
466 if (max_forwards > -1) {
467 snprintf(buf, BUFSIZ, "Max-Forwards: %d\r\n", max_forwards);
468 strcat(msg, buf);
469 }
470 struct base64_encode_ctx ctx;
471 base64_encode_init(&ctx);
472 size_t blen;
473 if (proxy_user) {
474 const char *user = proxy_user;
475 const char *password = proxy_password;
476 #if HAVE_GETPASS
477 if (!password)
478 password = getpass("Proxy password: ");
479 #endif
480 if (!password) {
481 std::cerr << "ERROR: Proxy password missing" << std::endl;
482 exit(1);
483 }
484 uint8_t *pwdBuf = new uint8_t[base64_encode_len(strlen(user)+1+strlen(password))];
485 blen = base64_encode_update(&ctx, pwdBuf, strlen(user), reinterpret_cast<const uint8_t*>(user));
486 blen += base64_encode_update(&ctx, pwdBuf+blen, 1, reinterpret_cast<const uint8_t*>(":"));
487 blen += base64_encode_update(&ctx, pwdBuf+blen, strlen(password), reinterpret_cast<const uint8_t*>(password));
488 blen += base64_encode_final(&ctx, pwdBuf+blen);
489 snprintf(buf, BUFSIZ, "Proxy-Authorization: Basic %.*s\r\n", (int)blen, reinterpret_cast<char*>(pwdBuf));
490 strcat(msg, buf);
491 delete[] pwdBuf;
492 }
493 if (www_user) {
494 const char *user = www_user;
495 const char *password = www_password;
496 #if HAVE_GETPASS
497 if (!password)
498 password = getpass("WWW password: ");
499 #endif
500 if (!password) {
501 std::cerr << "ERROR: WWW password missing" << std::endl;
502 exit(1);
503 }
504 uint8_t *pwdBuf = new uint8_t[base64_encode_len(strlen(user)+1+strlen(password))];
505 blen = base64_encode_update(&ctx, pwdBuf, strlen(user), reinterpret_cast<const uint8_t*>(user));
506 blen += base64_encode_update(&ctx, pwdBuf+blen, 1, reinterpret_cast<const uint8_t*>(":"));
507 blen += base64_encode_update(&ctx, pwdBuf+blen, strlen(password), reinterpret_cast<const uint8_t*>(password));
508 blen += base64_encode_final(&ctx, pwdBuf+blen);
509 snprintf(buf, BUFSIZ, "Authorization: Basic %.*s\r\n", (int)blen, reinterpret_cast<char*>(pwdBuf));
510 strcat(msg, buf);
511 delete[] pwdBuf;
512 }
513 #if HAVE_GSSAPI
514 if (www_neg) {
515 if (host) {
516 const char *token = GSSAPI_token(host);
517 snprintf(buf, BUFSIZ, "Authorization: Negotiate %s\r\n", token);
518 strcat(msg, buf);
519 delete[] token;
520 } else
521 std::cerr << "ERROR: server host missing" << std::endl;
522 }
523 if (proxy_neg) {
524 if (Transport::Config.hostname) {
525 const char *token = GSSAPI_token(Transport::Config.hostname);
526 snprintf(buf, BUFSIZ, "Proxy-Authorization: Negotiate %s\r\n", token);
527 strcat(msg, buf);
528 delete[] token;
529 } else
530 std::cerr << "ERROR: proxy server host missing" << std::endl;
531 }
532 #endif
533
534 /* HTTP/1.0 may need keep-alive explicitly */
535 if (strcmp(version, "1.0") == 0 && keep_alive)
536 strcat(msg, "Connection: keep-alive\r\n");
537
538 /* HTTP/1.1 may need close explicitly */
539 if (!keep_alive)
540 strcat(msg, "Connection: close\r\n");
541
542 strcat(msg, extra_hdrs);
543 strcat(msg, "\r\n");
544 }
545
546 debugVerbose(1, "Request:" << std::endl << msg << std::endl << ".");
547
548 uint32_t loops = Ping::Init();
549
550 for (uint32_t i = 0; loops == 0 || i < loops; ++i) {
551 size_t fsize = 0;
552
553 if (!Transport::Connect())
554 continue;
555
556 /* Send the HTTP request */
557 debugVerbose(2, "Sending HTTP request ... ");
558 bytesWritten = Transport::Write(msg, strlen(msg));
559
560 if (bytesWritten < 0) {
561 std::cerr << "ERROR: write" << std::endl;
562 exit(1);
563 } else if ((unsigned) bytesWritten != strlen(msg)) {
564 std::cerr << "ERROR: Cannot send request?: " << std::endl << msg << std::endl;
565 exit(1);
566 }
567 debugVerbose(2, "done.");
568
569 if (put_file) {
570 debugVerbose(1, "Sending HTTP request payload ...");
571 int x;
572 lseek(put_fd, 0, SEEK_SET);
573 while ((x = read(put_fd, buf, sizeof(buf))) > 0) {
574
575 x = Transport::Write(buf, x);
576
577 total_bytes += x;
578
579 if (x <= 0)
580 break;
581 }
582
583 if (x != 0)
584 std::cerr << "ERROR: Cannot send file." << std::endl;
585 else
586 debugVerbose(1, "done.");
587 }
588 /* Read the data */
589
590 #if _SQUID_WINDOWS_
591 setmode(1, O_BINARY);
592 #endif
593
594 while ((len = Transport::Read(buf, sizeof(buf))) > 0) {
595 fsize += len;
596
597 if (to_stdout && fwrite(buf, len, 1, stdout) != 1) {
598 int xerrno = errno;
599 std::cerr << "ERROR: writing to stdout: " << xstrerr(xerrno) << std::endl;
600 }
601 }
602
603 #if USE_GNUTLS
604 if (Transport::Config.tlsEnabled) {
605 if (len == 0) {
606 std::cerr << "- Peer has closed the TLS connection" << std::endl;
607 } else if (!gnutls_error_is_fatal(len)) {
608 std::cerr << "WARNING: " << gnutls_strerror(len) << std::endl;
609 } else {
610 std::cerr << "ERROR: " << gnutls_strerror(len) << std::endl;
611 }
612 }
613 #endif
614
615 #if _SQUID_WINDOWS_
616 setmode(1, O_TEXT);
617 #endif
618
619 Transport::CloseConnection();
620
621 if (Ping::LoopDone(i))
622 break;
623
624 Ping::TimerStop(fsize);
625 }
626
627 Ping::DisplayStats();
628 Transport::ShutdownTls();
629 return 0;
630 }
631
632 void
633 pipe_handler(int)
634 {
635 std::cerr << "SIGPIPE received." << std::endl;
636 }
637
638 static void
639 set_our_signal(void)
640 {
641 #if HAVE_SIGACTION
642 struct sigaction sa;
643 sa.sa_handler = pipe_handler;
644 sa.sa_flags = SA_RESTART;
645 sigemptyset(&sa.sa_mask);
646
647 if (sigaction(SIGPIPE, &sa, NULL) < 0) {
648 std::cerr << "ERROR: Cannot set PIPE signal." << std::endl;
649 exit(-1);
650 }
651 #else
652 signal(SIGPIPE, pipe_handler);
653 #endif
654 }
655