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

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors