squidclient.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2021 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"
16 #include "tools/squidclient/Ping.h"
18 
19 #if _SQUID_WINDOWS_
20 
21 using 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 */
61 static void usage(const char *progname);
62 
63 void pipe_handler(int sig);
64 static void set_our_signal(void);
65 
67 
68 static int put_fd;
69 static char *put_file = NULL;
70 
71 static struct stat sb;
72 int total_bytes = 0;
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  */
79 #endif
80 
81 #if _SQUID_WINDOWS_
82 void
83 Win32SockCleanup(void)
84 {
85  WSACleanup();
86  return;
87 }
88 #endif
89 
90 static void
91 usage(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 
131 static void
132 shellUnescape(char *buf)
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 {
184 public:
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 
197 void
198 Authorization::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 
228 static Authorization ProxyAuthorization("Proxy-Authorization", "proxy");
229 static Authorization OriginAuthorization("Authorization", "origin server");
230 
231 int
232 main(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 = NULL;
250  const char *version = "1.0";
251  const char *useragent = NULL;
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, 0, '?'},
271  {"verbose", no_argument, 0, 'v'},
272  {"quiet", no_argument, 0, 's'},
273  {"host", required_argument, 0, 'h'},
274  {"local", required_argument, 0, 'l'},
275  {"port", required_argument, 0, 'p'},
276  {"ping", no_argument, 0, '\1'},
277  {"https", no_argument, 0, '\3'},
278  {0, 0, 0, 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  }
419  }
420 #if _SQUID_WINDOWS_
421  {
422  WSADATA wsaData;
423  WSAStartup(2, &wsaData);
424  atexit(Win32SockCleanup);
425  }
426 #endif
427  /* Build the HTTP request */
428  if (strncmp(url, "mgr:", 4) == 0) {
429  char *t = xstrdup(url + 4);
430  const char *at = NULL;
431  if (!strrchr(t, '@')) { // ignore any -w password if @ is explicit already.
433  }
434  // embed the -w proxy password into old-style cachemgr URLs
435  if (at)
436  snprintf(url, sizeof(url), "cache_object://%s/%s@%s", Transport::Config.hostname, t, at);
437  else
438  snprintf(url, sizeof(url), "cache_object://%s/%s", Transport::Config.hostname, t);
439  xfree(t);
440  }
441  if (put_file) {
442  put_fd = open(put_file, O_RDONLY);
443  set_our_signal();
444 
445  if (put_fd < 0) {
446  int xerrno = errno;
447  std::cerr << "ERROR: can't open file (" << xstrerr(xerrno) << ")" << std::endl;
448  exit(EXIT_FAILURE);
449  }
450 #if _SQUID_WINDOWS_
451  setmode(put_fd, O_BINARY);
452 #endif
453 
454  if (fstat(put_fd, &sb) < 0) {
455  int xerrno = errno;
456  std::cerr << "ERROR: can't identify length of file (" << xstrerr(xerrno) << ")" << std::endl;
457  }
458  }
459 
460  if (!host) {
461  char *newhost = strstr(url, "://");
462  if (newhost) {
463  char *t;
464  newhost += 3;
465  newhost = xstrdup(newhost);
466  t = newhost + strcspn(newhost, "@/?");
467  if (*t == '@') {
468  newhost = t + 1;
469  t = newhost + strcspn(newhost, "@/?");
470  }
471  *t = '\0';
472  host = newhost;
473  }
474  }
475 
476  std::stringstream msg;
477 
478  if (version[0] == '-' || !version[0]) {
479  /* HTTP/0.9, no headers, no version */
480  msg << method << " " << url << "\r\n";
481  } else {
482  const auto versionImpliesHttp = xisdigit(version[0]); // is HTTP/n.n
483  msg << method << " "
484  << url << " "
485  << (versionImpliesHttp ? "HTTP/" : "") << version
486  << "\r\n";
487 
488  if (host) {
489  msg << "Host: " << host << "\r\n";
490  }
491 
492  if (!useragent) {
493  msg << "User-Agent: squidclient/" << VERSION << "\r\n";
494  } else if (useragent[0] != '\0') {
495  msg << "User-Agent: " << useragent << "\r\n";
496  } // else custom: no value U-A header
497 
498  if (reload) {
499  msg << "Cache-Control: no-cache\r\n";
500  }
501  if (put_fd > 0) {
502  msg << "Content-length: " << sb.st_size << "\r\n";
503  }
504  if (opt_noaccept == 0) {
505  msg << "Accept: */*\r\n";
506  }
507  if (ims) {
508  msg << "If-Modified-Since: " << mkrfc1123(ims) << "\r\n";
509  }
510  if (max_forwards > -1) {
511  msg << "Max-Forwards: " << max_forwards << "\r\n";
512  }
517 #if HAVE_GSSAPI
518  if (www_neg) {
519  if (host) {
520  const char *token = GSSAPI_token(host);
521  msg << "Proxy-Authorization: Negotiate " << token << "\r\n";
522  delete[] token;
523  } else
524  std::cerr << "ERROR: server host missing" << std::endl;
525  }
526  if (proxy_neg) {
527  if (Transport::Config.hostname) {
528  const char *token = GSSAPI_token(Transport::Config.hostname);
529  msg << "Proxy-Authorization: Negotiate " << token << "\r\n";
530  delete[] token;
531  } else
532  std::cerr << "ERROR: proxy server host missing" << std::endl;
533  }
534 #endif
535 
536  /* HTTP/1.0 may need keep-alive explicitly */
537  if (strcmp(version, "1.0") == 0 && keep_alive)
538  msg << "Connection: keep-alive\r\n";
539 
540  /* HTTP/1.1 may need close explicitly */
541  if (!keep_alive)
542  msg << "Connection: close\r\n";
543 
544  if (extra_hdrs) {
545  msg << extra_hdrs;
546  safe_free(extra_hdrs);
547  }
548  msg << "\r\n"; // empty line ends MIME header block
549  }
550 
551  msg.flush();
552  const auto messageHeader = msg.str();
553  debugVerbose(1, "Request:" << std::endl << messageHeader << std::endl << ".");
554 
555  uint32_t loops = Ping::Init();
556 
557  for (uint32_t i = 0; loops == 0 || i < loops; ++i) {
558  size_t fsize = 0;
559 
560  if (!Transport::Connect())
561  continue;
562 
563  /* Send the HTTP request */
564  debugVerbose(2, "Sending HTTP request ... ");
565  bytesWritten = Transport::Write(messageHeader.data(), messageHeader.length());
566 
567  if (bytesWritten < 0) {
568  std::cerr << "ERROR: write" << std::endl;
569  exit(EXIT_FAILURE);
570  } else if (static_cast<size_t>(bytesWritten) != messageHeader.length()) {
571  std::cerr << "ERROR: Failed to send the following request: " << std::endl
572  << messageHeader << std::endl;
573  exit(EXIT_FAILURE);
574  }
575  debugVerbose(2, "done.");
576 
577  if (put_file) {
578  debugVerbose(1, "Sending HTTP request payload ...");
579  int x;
580  if ((x = lseek(put_fd, 0, SEEK_SET)) < 0) {
581  int xerrno = errno;
582  std::cerr << "ERROR: lseek: " << xstrerr(xerrno) << std::endl;
583 
584  } else while ((x = read(put_fd, buf, sizeof(buf))) > 0) {
585 
586  x = Transport::Write(buf, x);
587 
588  total_bytes += x;
589 
590  if (x <= 0)
591  break;
592  }
593 
594  if (x != 0)
595  std::cerr << "ERROR: Cannot send file." << std::endl;
596  else
597  debugVerbose(1, "done.");
598  }
599  /* Read the data */
600 
601 #if _SQUID_WINDOWS_
602  setmode(1, O_BINARY);
603 #endif
604 
605  while ((len = Transport::Read(buf, sizeof(buf))) > 0) {
606  fsize += len;
607 
608  if (to_stdout && fwrite(buf, len, 1, stdout) != 1) {
609  int xerrno = errno;
610  std::cerr << "ERROR: writing to stdout: " << xstrerr(xerrno) << std::endl;
611  }
612  }
613 
614 #if USE_GNUTLS
615  if (Transport::Config.tlsEnabled) {
616  if (len == 0) {
617  std::cerr << "- Peer has closed the TLS connection" << std::endl;
618  } else if (!gnutls_error_is_fatal(len)) {
619  std::cerr << "WARNING: " << gnutls_strerror(len) << std::endl;
620  } else {
621  std::cerr << "ERROR: " << gnutls_strerror(len) << std::endl;
622  }
623  }
624 #endif
625 
626 #if _SQUID_WINDOWS_
627  setmode(1, O_TEXT);
628 #endif
629 
631 
632  if (Ping::LoopDone(i))
633  break;
634 
635  Ping::TimerStop(fsize);
636  }
637 
640  return EXIT_SUCCESS;
641 }
642 
643 void
645 {
646  std::cerr << "SIGPIPE received." << std::endl;
647 }
648 
649 static void
651 {
652 #if HAVE_SIGACTION
653  struct sigaction sa;
654  sa.sa_handler = pipe_handler;
655  sa.sa_flags = SA_RESTART;
656  sigemptyset(&sa.sa_mask);
657 
658  if (sigaction(SIGPIPE, &sa, NULL) < 0) {
659  std::cerr << "ERROR: Cannot set PIPE signal." << std::endl;
660  exit(EXIT_FAILURE);
661  }
662 #else
663  signal(SIGPIPE, pipe_handler);
664 #endif
665 }
666 
const char * xstrerr(int error)
Definition: xstrerror.cc:83
TheConfig Config
Definition: Transport.cc:23
#define base64_encode_len(length)
Definition: base64.h:169
void usage()
display Ping Options command line help to stderr
Definition: Ping.cc:155
static void usage(const char *progname)
Definition: squidclient.cc:91
static const char * shortOpStr
Definition: main.cc:421
int total_bytes
Definition: squidclient.cc:72
std::string header
HTTP header name to send.
Definition: squidclient.cc:191
size_t base64_encode_final(struct base64_encode_ctx *ctx, char *dst)
Definition: base64.c:308
#define O_TEXT
Definition: defines.h:140
static fde * Table
global table of FD and their state.
Definition: fde.h:103
void CloseConnection()
close the current connection
Definition: Transport.cc:285
bool parseCommandOpts(int argc, char *argv[], int c, int &optIndex)
Definition: Transport.cc:53
#define xstrdup
char * optarg
Definition: getopt.c:51
void usage()
display Transport Options command line help to stderr
Definition: Transport.cc:29
static void Win32SockCleanup(void)
Definition: WinSvc.cc:981
Definition: fde.h:52
ssize_t Read(void *buf, size_t len)
Definition: Transport.cc:262
uint32_t Init()
initialize the squidclient ping mode
Definition: Ping.cc:54
static Authorization ProxyAuthorization("Proxy-Authorization", "proxy")
#define O_BINARY
Definition: defines.h:143
#define NULL
Definition: types.h:166
static Authorization OriginAuthorization("Authorization", "origin server")
void ShutdownTls()
De-initialize TLS library environment when necessary.
Definition: Transport.cc:513
#define debugVerbose(LEVEL, MESSAGE)
display debug messages at varying verbosity levels
Definition: Parameters.h:31
bool LoopDone(int i)
whether ping loop is completed at the given iteration.
Definition: Ping.h:45
const char * user
user name to encode and send
Definition: squidclient.cc:193
int ioTimeout
I/O operation timeout.
Definition: Transport.h:50
bool parseCommandOpts(int argc, char *argv[], int c, int &optIndex)
Definition: Ping.cc:167
#define safe_free(x)
Definition: xalloc.h:73
#define assert(EX)
Definition: assert.h:19
static int put_fd
Definition: squidclient.cc:68
ssize_t Write(const void *buf, size_t len)
Definition: Transport.cc:238
static int version
#define xisdigit(x)
Definition: xis.h:20
#define BUFSIZ
Definition: squidclient.cc:57
#define SA_RESTART
void pipe_handler(int sig)
Definition: squidclient.cc:644
void base64_encode_init(struct base64_encode_ctx *ctx)
Definition: base64.c:232
#define xfree
const char * mkrfc1123(time_t)
Definition: rfc1123.c:202
[Proxy-]Authorization header producer
Definition: squidclient.cc:183
void TimerStop(size_t fsize)
Definition: Ping.cc:92
static void shellUnescape(char *buf)
Definition: squidclient.cc:132
static void set_our_signal(void)
Definition: squidclient.cc:650
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 optind
Definition: getopt.c:48
Ping::TheConfig Config
Definition: Ping.cc:24
size_t base64_encode_update(struct base64_encode_ctx *ctx, char *dst, size_t length, const uint8_t *src)
Definition: base64.c:265
Parameters scParams
global squidcleint parameters
Definition: squidclient.cc:66
int verbosityLevel
Definition: Parameters.h:27
int main(int argc, char *argv[])
Definition: squidclient.cc:232
static char * put_file
Definition: squidclient.cc:69
#define VERSION
static struct stat sb
Definition: squidclient.cc:71
std::string destination
used when describing password
Definition: squidclient.cc:192
char progname[]
void commit(std::ostream &os)
finalizes and writes the right HTTP header to the given stream
Definition: squidclient.cc:198
void ProbeTransport(void)
Probe to discover IPv6 capabilities.
void DisplayStats()
display summary of ping data collected
Definition: Ping.cc:141
bool Connect()
locate and connect to the configured server
Definition: Transport.cc:214

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors