cachemgr.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 1996-2022 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 "base/CharacterSet.h"
11 #include "base64.h"
12 #include "getfullhostname.h"
13 #include "html_quote.h"
14 #include "ip/Address.h"
15 #include "MemBuf.h"
16 #include "rfc1738.h"
17 #include "time/gadgets.h"
18 #include "util.h"
19 
20 #include <cctype>
21 #include <cerrno>
22 #include <csignal>
23 #include <cstring>
24 #include <ctime>
25 #if HAVE_UNISTD_H
26 #include <unistd.h>
27 #endif
28 #if HAVE_FCNTL_H
29 #include <fcntl.h>
30 #endif
31 #if HAVE_GRP_H
32 #include <grp.h>
33 #endif
34 #if HAVE_GNUMALLOC_H
35 #include <gnumalloc.h>
36 #elif HAVE_MALLOC_H
37 #include <malloc.h>
38 #endif
39 #if HAVE_MEMORY_H
40 #include <memory.h>
41 #endif
42 #if HAVE_NETDB_H
43 #include <netdb.h>
44 #endif
45 #if HAVE_PWD_H
46 #include <pwd.h>
47 #endif
48 #if HAVE_SYS_PARAM_H
49 #include <sys/param.h>
50 #endif
51 #if HAVE_SYS_SOCKET_H
52 #include <sys/socket.h>
53 #endif
54 #if HAVE_NETINET_IN_H
55 #include <netinet/in.h>
56 #endif
57 #if HAVE_ARPA_INET_H
58 #include <arpa/inet.h>
59 #endif
60 #if HAVE_SYS_STAT_H
61 #include <sys/stat.h>
62 #endif
63 #if HAVE_SYS_UN_H
64 #include <sys/un.h>
65 #endif
66 #if HAVE_SYS_WAIT_H
67 #include <sys/wait.h>
68 #endif
69 #if HAVE_LIBC_H
70 #include <libc.h>
71 #endif
72 #if HAVE_STRINGS_H
73 #include <strings.h>
74 #endif
75 #if HAVE_BSTRING_H
76 #include <bstring.h>
77 #endif
78 #if HAVE_CRYPT_H
79 #include <crypt.h>
80 #endif
81 #if HAVE_FNMATCH_H
82 extern "C" {
83 #include <fnmatch.h>
84 }
85 #endif
86 
87 #ifndef DEFAULT_CACHEMGR_CONFIG
88 #define DEFAULT_CACHEMGR_CONFIG "/etc/squid/cachemgr.conf"
89 #endif
90 
91 typedef struct {
92  char *server;
93  char *hostname;
94  int port;
95  char *action;
96  char *user_name;
97  char *passwd;
98  char *pub_auth;
99  char *workers;
100  char *processes;
102 
103 /*
104  * Static variables and constants
105  */
106 static const time_t passwd_ttl = 60 * 60 * 3; /* in sec */
107 static const char *script_name = "/cgi-bin/cachemgr.cgi";
108 static const char *progname = nullptr;
109 static time_t now;
110 
111 /*
112  * Function prototypes
113  */
114 static const char *safe_str(const char *str);
115 static const char *xstrtok(char **str, char del);
116 static void print_trailer(void);
117 static void auth_html(const char *host, int port, const char *user_name);
118 static void error_html(const char *msg);
119 static char *menu_url(cachemgr_request * req, const char *action);
120 static int parse_status_line(const char *sline, const char **statusStr);
121 static cachemgr_request *read_request(void);
122 static char *read_get_request(void);
123 static char *read_post_request(void);
124 
125 static void make_pub_auth(cachemgr_request * req);
126 static void decode_pub_auth(cachemgr_request * req);
127 static void reset_auth(cachemgr_request * req);
128 static const char *make_auth_header(const cachemgr_request * req);
129 
130 static int check_target_acl(const char *hostname, int port);
131 
132 #if _SQUID_WINDOWS_
133 static int s_iInitCount = 0;
134 
135 int Win32SockInit(void)
136 {
137  int iVersionRequested;
138  WSADATA wsaData;
139  int err;
140 
141  if (s_iInitCount > 0) {
142  ++s_iInitCount;
143  return (0);
144  } else if (s_iInitCount < 0)
145  return (s_iInitCount);
146 
147  /* s_iInitCount == 0. Do the initialization */
148  iVersionRequested = MAKEWORD(2, 0);
149 
150  err = WSAStartup((WORD) iVersionRequested, &wsaData);
151 
152  if (err) {
153  s_iInitCount = -1;
154  return (s_iInitCount);
155  }
156 
157  if (LOBYTE(wsaData.wVersion) != 2 ||
158  HIBYTE(wsaData.wVersion) != 0) {
159  s_iInitCount = -2;
160  WSACleanup();
161  return (s_iInitCount);
162  }
163 
164  ++s_iInitCount;
165  return (s_iInitCount);
166 }
167 
168 void Win32SockCleanup(void)
169 {
170  if (--s_iInitCount == 0)
171  WSACleanup();
172 
173  return;
174 }
175 
176 #endif
177 
178 static const char *
179 safe_str(const char *str)
180 {
181  return str ? str : "";
182 }
183 
184 /* relaxed number format */
185 static int
186 is_number(const char *str)
187 {
188  return strspn(str, "\t -+01234567890./\n") == strlen(str);
189 }
190 
191 static const char *
192 xstrtok(char **str, char del)
193 {
194  if (*str) {
195  char *p = strchr(*str, del);
196  char *tok = *str;
197  int len;
198 
199  if (p) {
200  *str = p + 1;
201  *p = '\0';
202  } else
203  *str = nullptr;
204 
205  /* trim */
206  len = strlen(tok);
207 
208  while (len && xisspace(tok[len - 1]))
209  tok[--len] = '\0';
210 
211  while (xisspace(*tok))
212  ++tok;
213 
214  return tok;
215  } else
216  return "";
217 }
218 
219 static bool
220 hostname_check(const char *uri)
221 {
222  static CharacterSet hostChars = CharacterSet("host",".:[]_") +
224 
225  const auto limit = strlen(uri);
226  for (size_t i = 0; i < limit; i++) {
227  if (!hostChars[uri[i]]) {
228  return false;
229  }
230  }
231  return true;
232 }
233 
234 static void
236 {
237  printf("<HR noshade size=\"1px\">\n");
238  printf("<ADDRESS>\n");
239  printf("Generated %s, by %s/%s@%s\n",
241  printf("</ADDRESS></BODY></HTML>\n");
242 }
243 
244 static void
245 auth_html(const char *host, int port, const char *user_name)
246 {
247  FILE *fp;
248  int need_host = 1;
249 
250  if (!user_name)
251  user_name = "";
252 
253  if (!host || !strlen(host))
254  host = "";
255 
256  fp = fopen("cachemgr.conf", "r");
257 
258  if (fp == nullptr)
259  fp = fopen(DEFAULT_CACHEMGR_CONFIG, "r");
260 
261  if (fp == nullptr)
262  printf("X-Error: message=\"Unable to open config %s\"", DEFAULT_CACHEMGR_CONFIG);
263 
264  printf("Content-Type: text/html\r\n\r\n");
265 
266  printf("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n");
267 
268  printf("<HTML><HEAD><TITLE>Cache Manager Interface</TITLE>\n");
269 
270  printf("<STYLE type=\"text/css\"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}--></STYLE>\n");
271 
272  printf("<script type=\"text/javascript\">\n");
273  printf("function TS(t, s) {\n");
274  printf(" var x = new XMLHttpRequest();\n");
275  printf(" x.open('GET', 'http' + s + '://' + t + '/squid-internal-mgr/', true);\n");
276  printf(" x.onreadystatechange=function() {\n");
277  printf(" if (x.readyState==4) {\n");
278  printf(" if ((x.status>=200 && x.status <= 299) || x.status==401) {\n");
279  printf(" var v = x.getResponseHeader('Server');\n");
280  printf(" if (v.substring(0,8) == 'squid/3.' && (v[8]=='H' || parseInt(v.substring(8)) >= 2)) {\n");
281  printf(" var d = document.getElementById('H' + s + 'mgr');\n");
282  printf(" if (d.innerHTML == '') d.innerHTML = '<h2>HTTP' + (s=='s'?'S':'') + ' Managed Proxies</h2>';\n");
283  printf(" d.innerHTML = d.innerHTML + '<p>Host: <a href=\"http' + s + '://' + t + '/squid-internal-mgr/\">' + t + '</a></p>';\n");
284  printf(" }}}}\n");
285  printf(" x.send(null);\n");
286  printf("}\n");
287  printf("</script>\n");
288 
289  printf("</HEAD>\n");
290 
291  printf("<BODY><H1>Cache Manager Interface</H1>\n");
292 
293  printf("<P>This is a WWW interface to the instrumentation interface\n");
294 
295  printf("for the Squid object cache.</P>\n");
296 
297  printf("<HR noshade size=\"1px\">\n");
298 
299  printf("<div id=\"Hsmgr\"></div>\n");
300  printf("<div id=\"Hmgr\"></div>\n");
301  printf("<div id=\"Cmgr\">\n");
302  printf("<h2>CGI Managed Proxies</h2>\n");
303  printf("<FORM METHOD=\"POST\" ACTION=\"%s\">\n", script_name);
304 
305  printf("<TABLE BORDER=\"0\" CELLPADDING=\"10\" CELLSPACING=\"1\">\n");
306 
307  if (fp != nullptr) {
308  int servers = 0;
309  char config_line[BUFSIZ];
310 
311  while (fgets(config_line, BUFSIZ, fp)) {
312  char *server, *comment;
313  if (strtok(config_line, "\r\n") == nullptr)
314  continue;
315 
316  if (config_line[0] == '#')
317  continue;
318 
319  if (config_line[0] == '\0')
320  continue;
321 
322  if ((server = strtok(config_line, " \t")) == nullptr)
323  continue;
324 
325  if (strchr(server, '*') || strchr(server, '[') || strchr(server, '?')) {
326  need_host = -1;
327  continue;
328  }
329 
330  comment = strtok(nullptr, "");
331 
332  if (comment)
333  while (*comment == ' ' || *comment == '\t')
334  ++comment;
335 
336  if (!comment || !*comment)
337  comment = server;
338 
339  if (!servers)
340  printf("<TR><TH ALIGN=\"left\">Cache Server:</TH><TD><SELECT id=\"server\" NAME=\"server\">\n");
341 
342  printf("<OPTION VALUE=\"%s\"%s>%s</OPTION>\n", server, (servers || *host) ? "" : " SELECTED", comment);
343  ++servers;
344  }
345 
346  if (servers) {
347  if (need_host == 1 && !*host)
348  need_host = 0;
349 
350  if (need_host)
351  printf("<OPTION VALUE=\"\"%s>Other</OPTION>\n", (*host) ? " SELECTED" : "");
352 
353  printf("</SELECT></TR>\n");
354  }
355 
356  fclose(fp);
357  }
358 
359  if (need_host) {
360  if (need_host == 1 && !*host)
361  host = "localhost";
362 
363  printf("<TR><TH ALIGN=\"left\">Cache Host:</TH><TD><INPUT NAME=\"host\" ");
364 
365  printf("size=\"30\" VALUE=\"%s\"></TD></TR>\n", host);
366 
367  printf("<TR><TH ALIGN=\"left\">Cache Port:</TH><TD><INPUT NAME=\"port\" ");
368 
369  printf("size=\"30\" VALUE=\"%d\"></TD></TR>\n", port);
370  }
371 
372  printf("<TR><TH ALIGN=\"left\">Manager name:</TH><TD><INPUT NAME=\"user_name\" ");
373 
374  printf("size=\"30\" VALUE=\"%s\"></TD></TR>\n", rfc1738_escape(user_name));
375 
376  printf("<TR><TH ALIGN=\"left\">Password:</TH><TD><INPUT TYPE=\"password\" NAME=\"passwd\" ");
377 
378  printf("size=\"30\" VALUE=\"\"></TD></TR>\n");
379 
380  printf("</TABLE><BR CLEAR=\"all\">\n");
381 
382  printf("<INPUT TYPE=\"submit\" VALUE=\"Continue...\">\n");
383 
384  printf("</FORM></div>\n");
385 
386  printf("<script type=\"text/javascript\">\n");
387  printf("var s = document.getElementById(\"server\");\n");
388  printf("for (var i = 0; i < s.childElementCount; i++) {\n");
389  printf(" TS(s.children[i].value, '');\n");
390  printf(" TS(s.children[i].value, 's');\n");
391  printf("}</script>\n");
392 
393  print_trailer();
394 }
395 
396 static void
397 error_html(const char *msg)
398 {
399  printf("Content-Type: text/html\r\n\r\n");
400  printf("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n");
401  printf("<HTML><HEAD><TITLE>Cache Manager Error</TITLE>\n");
402  printf("<STYLE type=\"text/css\"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}--></STYLE></HEAD>\n");
403  printf("<BODY><H1>Cache Manager Error</H1>\n");
404  printf("<P>\n%s</P>\n", html_quote(msg));
405  print_trailer();
406 }
407 
408 /* returns http status extracted from status line or -1 on parsing failure */
409 static int
410 parse_status_line(const char *sline, const char **statusStr)
411 {
412  const char *sp = strchr(sline, ' ');
413 
414  if (statusStr)
415  *statusStr = nullptr;
416 
417  if (strncasecmp(sline, "HTTP/", 5) || !sp)
418  return -1;
419 
420  while (xisspace(*++sp));
421  if (!xisdigit(*sp))
422  return -1;
423 
424  if (statusStr)
425  *statusStr = sp;
426 
427  return atoi(sp);
428 }
429 
430 static char *
431 menu_url(cachemgr_request * req, const char *action)
432 {
433  static char url[1024];
434  snprintf(url, sizeof(url), "%s?host=%s&port=%d&user_name=%s&operation=%s&auth=%s",
435  script_name,
436  req->hostname,
437  req->port,
439  action,
440  safe_str(req->pub_auth));
441  return url;
442 }
443 
444 static void
445 munge_menu_line(MemBuf &out, const char *buf, cachemgr_request * req)
446 {
447  char *x;
448  const char *a;
449  const char *d;
450  const char *p;
451  char *a_url;
452  char *buf_copy;
453 
454  const char bufLen = strlen(buf);
455  if (bufLen < 1 || *buf != ' ') {
456  out.append(buf, bufLen);
457  return;
458  }
459 
460  buf_copy = x = xstrndup(buf, bufLen+1);
461 
462  a = xstrtok(&x, '\t');
463 
464  d = xstrtok(&x, '\t');
465 
466  p = xstrtok(&x, '\t');
467 
468  a_url = xstrdup(menu_url(req, a));
469 
470  /* no reason to give a url for a disabled action */
471  if (!strcmp(p, "disabled"))
472  out.appendf("<LI type=\"circle\">%s (disabled)<A HREF=\"%s\">.</A>\n", d, a_url);
473  else
474  /* disable a hidden action (requires a password, but password is not in squid.conf) */
475  if (!strcmp(p, "hidden"))
476  out.appendf("<LI type=\"circle\">%s (hidden)<A HREF=\"%s\">.</A>\n", d, a_url);
477  else
478  /* disable link if authentication is required and we have no password */
479  if (!strcmp(p, "protected") && !req->passwd)
480  out.appendf("<LI type=\"circle\">%s (requires <a href=\"%s\">authentication</a>)<A HREF=\"%s\">.</A>\n",
481  d, menu_url(req, "authenticate"), a_url);
482  else
483  /* highlight protected but probably available entries */
484  if (!strcmp(p, "protected"))
485  out.appendf("<LI type=\"square\"><A HREF=\"%s\"><font color=\"#FF0000\">%s</font></A>\n",
486  a_url, d);
487 
488  /* public entry or unknown type of protection */
489  else
490  out.appendf("<LI type=\"disk\"><A HREF=\"%s\">%s</A>\n", a_url, d);
491 
492  xfree(a_url);
493 
494  xfree(buf_copy);
495 }
496 
497 static void
498 munge_other_line(MemBuf &out, const char *buf, cachemgr_request *)
499 {
500  static const char *ttags[] = {"td", "th"};
501 
502  static int table_line_num = 0;
503  static int next_is_header = 0;
504  int is_header = 0;
505  const char *ttag;
506  char *buf_copy;
507  char *x, *p;
508  /* does it look like a table? */
509 
510  if (!strchr(buf, '\t') || *buf == '\t') {
511  /* nope, just text */
512  if (table_line_num)
513  out.append("</table>\n<pre>", 14);
514  out.appendf("%s", html_quote(buf));
515  table_line_num = 0;
516  return;
517  }
518 
519  /* start html table */
520  if (!table_line_num) {
521  out.append("</pre><table cellpadding=\"2\" cellspacing=\"1\">\n", 46);
522  next_is_header = 0;
523  }
524 
525  /* remove '\n' */
526  is_header = (!table_line_num || next_is_header) && !strchr(buf, ':') && !is_number(buf);
527 
528  ttag = ttags[is_header];
529 
530  /* record starts */
531  out.append("<tr>", 4);
532 
533  /* substitute '\t' */
534  buf_copy = x = xstrdup(buf);
535 
536  if ((p = strchr(x, '\n')))
537  *p = '\0';
538 
539  while (x && strlen(x)) {
540  int column_span = 1;
541  const char *cell = xstrtok(&x, '\t');
542 
543  while (x && *x == '\t') {
544  ++column_span;
545  ++x;
546  }
547 
548  out.appendf("<%s colspan=\"%d\" align=\"%s\">%s</%s>",
549  ttag, column_span,
550  is_header ? "center" : is_number(cell) ? "right" : "left",
551  html_quote(cell), ttag);
552  }
553 
554  xfree(buf_copy);
555  /* record ends */
556  out.append("</tr>\n", 6);
557  next_is_header = is_header && strstr(buf, "\t\t");
558  ++table_line_num;
559 }
560 
561 static const char *
562 munge_action_line(const char *_buf, cachemgr_request * req)
563 {
564  static char html[2 * 1024];
565  char *buf = xstrdup(_buf);
566  char *x = buf;
567  const char *action, *description;
568  char *p;
569 
570  if ((p = strchr(x, '\n')))
571  *p = '\0';
572  action = xstrtok(&x, '\t');
573  if (!action) {
574  xfree(buf);
575  return "";
576  }
577  description = xstrtok(&x, '\t');
578  if (!description)
579  description = action;
580  snprintf(html, sizeof(html), " <a href=\"%s\">%s</a>", menu_url(req, action), description);
581  xfree(buf);
582  return html;
583 }
584 
585 static int
587 {
588  char buf[4 * 1024];
589 #if _SQUID_WINDOWS_
590 
591  int reply;
592  char *tmpfile = tempnam(NULL, "tmp0000");
593  FILE *fp = fopen(tmpfile, "w+");
594 #else
595 
596  FILE *fp = fdopen(s, "r");
597 #endif
598  /* interpretation states */
599  enum {
600  isStatusLine, isHeaders, isActions, isBodyStart, isBody, isForward, isEof, isForwardEof, isSuccess, isError
601  } istate = isStatusLine;
602  int parse_menu = 0;
603  const char *action = req->action;
604  const char *statusStr = nullptr;
605  int status = -1;
606 
607  if (0 == strlen(req->action))
608  parse_menu = 1;
609  else if (0 == strcasecmp(req->action, "menu"))
610  parse_menu = 1;
611 
612  if (fp == nullptr) {
613 #if _SQUID_WINDOWS_
614  perror(tmpfile);
615  xfree(tmpfile);
616 #else
617 
618  perror("fdopen");
619 #endif
620 
621  close(s);
622  return 1;
623  }
624 
625 #if _SQUID_WINDOWS_
626 
627  while ((reply=recv(s, buf, sizeof(buf), 0)) > 0)
628  fwrite(buf, 1, reply, fp);
629 
630  rewind(fp);
631 
632 #endif
633 
634  if (parse_menu)
635  action = "menu";
636 
637  /* read reply interpreting one line at a time depending on state */
638  while (istate < isEof) {
639  if (!fgets(buf, sizeof(buf), fp))
640  istate = istate == isForward ? isForwardEof : isEof;
641 
642  switch (istate) {
643 
644  case isStatusLine:
645  /* get HTTP status */
646  /* uncomment the following if you want to debug headers */
647  /* fputs("\r\n\r\n", stdout); */
648  status = parse_status_line(buf, &statusStr);
649  istate = status == 200 ? isHeaders : isForward;
650  /* if cache asks for authentication, we have to reset our info */
651 
652  if (status == 401 || status == 407) {
653  reset_auth(req);
654  status = 403; /* Forbidden, see comments in case isForward: */
655  }
656 
657  /* this is a way to pass HTTP status to the Web server */
658  if (statusStr)
659  printf("Status: %d %s", status, statusStr); /* statusStr has '\n' */
660 
661  break;
662 
663  case isHeaders:
664  /* forward header field */
665  if (!strcmp(buf, "\r\n")) { /* end of headers */
666  fputs("Content-Type: text/html\r\n", stdout); /* add our type */
667  istate = isBodyStart;
668  }
669 
670  if (strncasecmp(buf, "Content-Type:", 13)) /* filter out their type */
671  fputs(buf, stdout);
672 
673  break;
674 
675  case isBodyStart:
676  printf("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n");
677 
678  printf("<HTML><HEAD><TITLE>CacheMgr@%s: %s</TITLE>\n",
679  req->hostname, action);
680 
681  printf("<STYLE type=\"text/css\"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}TABLE{background-color:#333333;border:0pt;padding:0pt}TH,TD{background-color:#ffffff;white-space:nowrap}--></STYLE>\n");
682 
683  printf("</HEAD><BODY>\n");
684 
685  if (parse_menu) {
686  printf("<H2><a href=\"%s\">Cache Manager</a> menu for %s:</H2>",
687  menu_url(req, "authenticate"), req->hostname);
688  printf("<UL>\n");
689  } else {
690  printf("<P><A HREF=\"%s\">%s</A>\n<HR noshade size=\"1px\">\n",
691  menu_url(req, "menu"), "Cache Manager menu");
692  printf("<PRE>\n");
693  }
694 
695  istate = isActions;
696  /* [[fallthrough]] we do not want to lose the first line */
697 
698  case isActions:
699  if (strncmp(buf, "action:", 7) == 0) {
700  fputs(" ", stdout);
701  fputs(munge_action_line(buf + 7, req), stdout);
702  break;
703  }
704  if (parse_menu) {
705  printf("<UL>\n");
706  } else {
707  printf("<HR noshade size=\"1px\">\n");
708  printf("<PRE>\n");
709  }
710 
711  istate = isBody;
712  /* [[fallthrough]] we do not want to lose the first line */
713 
714  case isBody:
715  {
716  /* interpret [and reformat] cache response */
717  MemBuf out;
718  out.init();
719  if (parse_menu)
720  munge_menu_line(out, buf, req);
721  else
722  munge_other_line(out, buf, req);
723 
724  fputs(out.buf, stdout);
725  }
726  break;
727 
728  case isForward:
729  /* forward: no modifications allowed */
730  /*
731  * Note: we currently do not know any way to get browser.reply to
732  * 401 to .cgi because web server filters out all auth info. Thus we
733  * disable authentication headers for now.
734  */
735  if (!strncasecmp(buf, "WWW-Authenticate:", 17) || !strncasecmp(buf, "Proxy-Authenticate:", 19)); /* skip */
736  else
737  fputs(buf, stdout);
738 
739  break;
740 
741  case isEof:
742  /* print trailers */
743  if (parse_menu)
744  printf("</UL>\n");
745  else
746  printf("</table></PRE>\n");
747 
748  print_trailer();
749 
750  istate = isSuccess;
751 
752  break;
753 
754  case isForwardEof:
755  /* indicate that we finished processing an "error" sequence */
756  istate = isError;
757 
758  break;
759 
760  default:
761  printf("%s: internal bug: invalid state reached: %d", script_name, istate);
762 
763  istate = isError;
764  }
765  }
766 
767  fclose(fp);
768 #if _SQUID_WINDOWS_
769 
770  remove(tmpfile);
771  xfree(tmpfile);
772  close(s);
773 
774 #endif
775 
776  return 0;
777 }
778 
779 static int
781 {
782 
783  char ipbuf[MAX_IPSTRLEN];
784  struct addrinfo *AI = nullptr;
785  Ip::Address S;
786  int s;
787  int l;
788 
789  static char buf[2 * 1024];
790 
791  if (req == nullptr) {
793  return 1;
794  }
795 
796  if (req->hostname == nullptr) {
798  }
799 
800  if (req->port == 0) {
801  req->port = CACHE_HTTP_PORT;
802  }
803 
804  if (req->action == nullptr) {
805  req->action = xstrdup("");
806  }
807 
808  if (strcmp(req->action, "authenticate") == 0) {
809  auth_html(req->hostname, req->port, req->user_name);
810  return 0;
811  }
812 
813  if (!check_target_acl(req->hostname, req->port)) {
814  snprintf(buf, sizeof(buf), "target %s:%d not allowed in cachemgr.conf\n", req->hostname, req->port);
815  error_html(buf);
816  return 1;
817  }
818 
819  S = *gethostbyname(req->hostname);
820 
821  if ( !S.isAnyAddr() ) {
822  (void) 0;
823  } else if ((S = req->hostname))
824  (void) 0;
825  else {
826  if (hostname_check(req->hostname)) {
827  snprintf(buf, sizeof(buf), "Unknown Host: %s\n", req->hostname);
828  error_html(buf);
829  return 1;
830  } else {
831  snprintf(buf, sizeof(buf), "%s\n", "Invalid Hostname");
832  error_html(buf);
833  return 1;
834  }
835  }
836 
837  S.port(req->port);
838 
839  S.getAddrInfo(AI);
840 
841 #if USE_IPV6
842  if ((s = socket( AI->ai_family, SOCK_STREAM, 0)) < 0) {
843 #else
844  if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
845 #endif
846  int xerrno = errno;
847  snprintf(buf, sizeof(buf), "socket: %s\n", xstrerr(xerrno));
848  error_html(buf);
850  return 1;
851  }
852 
853  if (connect(s, AI->ai_addr, AI->ai_addrlen) < 0) {
854  int xerrno = errno;
855  snprintf(buf, sizeof(buf), "connect %s: %s\n", S.toUrl(ipbuf,MAX_IPSTRLEN), xstrerr(xerrno));
856  error_html(buf);
858  close(s);
859  return 1;
860  }
861 
863 
864  l = snprintf(buf, sizeof(buf),
865  "GET cache_object://%s/%s%s%s HTTP/1.0\r\n"
866  "User-Agent: cachemgr.cgi/%s\r\n"
867  "Accept: */*\r\n"
868  "%s" /* Authentication info or nothing */
869  "\r\n",
870  req->hostname,
871  req->action,
872  req->workers? "?workers=" : (req->processes ? "?processes=" : ""),
873  req->workers? req->workers : (req->processes ? req->processes: ""),
874  VERSION,
875  make_auth_header(req));
876  if (write(s, buf, l) < 0) {
877  fprintf(stderr,"ERROR: (%d) writing request: '%s'\n", errno, buf);
878  } else {
879  debug("wrote request: '%s'\n", buf);
880  }
881  return read_reply(s, req);
882 }
883 
884 int
885 main(int argc, char *argv[])
886 {
887  char *s;
888  cachemgr_request *req;
889 
890  now = time(nullptr);
891 #if _SQUID_WINDOWS_
892 
893  Win32SockInit();
894  atexit(Win32SockCleanup);
895  _setmode( _fileno( stdin ), _O_BINARY );
896  _setmode( _fileno( stdout ), _O_BINARY );
897  _fmode = _O_BINARY;
898 
899  if ((s = strrchr(argv[0], '\\')))
900 #else
901 
902  if ((s = strrchr(argv[0], '/')))
903 #endif
904 
905  progname = xstrdup(s + 1);
906  else
907  progname = xstrdup(argv[0]);
908 
909  if ((s = getenv("SCRIPT_NAME")) != nullptr)
910  script_name = xstrdup(s);
911 
912  char **args = argv;
913  while (argc > 1 && args[1][0] == '-') {
914  char option = args[1][1];
915  switch (option) {
916  case 'd':
917  debug_enabled = 1;
918  break;
919  default:
920  break;
921  }
922  ++args;
923  --argc;
924  }
925 
926  req = read_request();
927 
928  return process_request(req);
929 }
930 
931 static char *
933 {
934  char *s;
935 
936  if ((s = getenv("REQUEST_METHOD")) == nullptr)
937  return nullptr;
938 
939  if (0 != strcasecmp(s, "POST"))
940  return nullptr;
941 
942  if ((s = getenv("CONTENT_LENGTH")) == nullptr)
943  return nullptr;
944 
945  if (*s == '-') // negative length content huh?
946  return nullptr;
947 
948  uint64_t len;
949 
950  char *endptr = s+ strlen(s);
951  if ((len = strtoll(s, &endptr, 10)) <= 0)
952  return nullptr;
953 
954  // limit the input to something reasonable.
955  // 4KB should be enough for the GET/POST data length, but may be extended.
956  size_t bufLen = (len < 4096 ? len : 4095);
957  char *buf = (char *)xmalloc(bufLen + 1);
958 
959  size_t readLen = fread(buf, 1, bufLen, stdin);
960  if (readLen == 0) {
961  xfree(buf);
962  return nullptr;
963  }
964  buf[readLen] = '\0';
965  len -= readLen;
966 
967  // purge the remainder of the request entity
968  while (len > 0 && readLen) {
969  char temp[65535];
970  readLen = fread(temp, 1, 65535, stdin);
971  len -= readLen;
972  }
973 
974  return buf;
975 }
976 
977 static char *
979 {
980  char *s;
981 
982  if ((s = getenv("QUERY_STRING")) == nullptr)
983  return nullptr;
984 
985  return xstrdup(s);
986 }
987 
988 static cachemgr_request *
990 {
991  char *buf;
992 
993  cachemgr_request *req;
994  char *s;
995  char *t = nullptr;
996  char *q;
997 
998  if ((buf = read_post_request()) != nullptr)
999  (void) 0;
1000  else if ((buf = read_get_request()) != nullptr)
1001  (void) 0;
1002  else
1003  return nullptr;
1004 
1005 #if _SQUID_WINDOWS_
1006 
1007  if (strlen(buf) == 0 || strlen(buf) == 4000)
1008 #else
1009 
1010  if (strlen(buf) == 0)
1011 #endif
1012  {
1013  xfree(buf);
1014  return nullptr;
1015  }
1016 
1017  req = (cachemgr_request *)xcalloc(1, sizeof(cachemgr_request));
1018 
1019  for (s = strtok(buf, "&"); s != nullptr; s = strtok(nullptr, "&")) {
1020  safe_free(t);
1021  t = xstrdup(s);
1022 
1023  if ((q = strchr(t, '=')) == nullptr)
1024  continue;
1025 
1026  *q = '\0';
1027  ++q;
1028 
1029  rfc1738_unescape(t);
1030 
1031  rfc1738_unescape(q);
1032 
1033  if (0 == strcmp(t, "server") && strlen(q))
1034  req->server = xstrdup(q);
1035  else if (0 == strcmp(t, "host") && strlen(q))
1036  req->hostname = xstrdup(q);
1037  else if (0 == strcmp(t, "port") && strlen(q))
1038  req->port = atoi(q);
1039  else if (0 == strcmp(t, "user_name") && strlen(q))
1040  req->user_name = xstrdup(q);
1041  else if (0 == strcmp(t, "passwd") && strlen(q))
1042  req->passwd = xstrdup(q);
1043  else if (0 == strcmp(t, "auth") && strlen(q))
1044  req->pub_auth = xstrdup(q), decode_pub_auth(req);
1045  else if (0 == strcmp(t, "operation"))
1046  req->action = xstrdup(q);
1047  else if (0 == strcmp(t, "workers") && strlen(q))
1048  req->workers = xstrdup(q);
1049  else if (0 == strcmp(t, "processes") && strlen(q))
1050  req->processes = xstrdup(q);
1051  }
1052  safe_free(t);
1053 
1054  if (req->server && !req->hostname) {
1055  char *p;
1056  req->hostname = strtok(req->server, ":");
1057 
1058  if ((p = strtok(nullptr, ":")))
1059  req->port = atoi(p);
1060  }
1061 
1062  make_pub_auth(req);
1063  debug("cmgr: got req: host: '%s' port: %d uname: '%s' passwd: '%s' auth: '%s' oper: '%s' workers: '%s' processes: '%s'\n",
1064  safe_str(req->hostname), req->port, safe_str(req->user_name), safe_str(req->passwd), safe_str(req->pub_auth), safe_str(req->action), safe_str(req->workers), safe_str(req->processes));
1065  return req;
1066 }
1067 
1068 /* Routines to support authentication */
1069 
1070 /*
1071  * Encodes auth info into a "public" form.
1072  * Currently no powerful encryption is used.
1073  */
1074 static void
1076 {
1077  static char buf[1024];
1078  safe_free(req->pub_auth);
1079  debug("cmgr: encoding for pub...\n");
1080 
1081  if (!req->passwd || !strlen(req->passwd))
1082  return;
1083 
1084  auto *rfc1738_username = xstrdup(rfc1738_escape(safe_str(req->user_name)));
1085  auto *rfc1738_passwd = xstrdup(rfc1738_escape(req->passwd));
1086 
1087  /* host | time | user | passwd */
1088  const int bufLen = snprintf(buf, sizeof(buf), "%s|%d|%s|%s",
1089  req->hostname,
1090  (int) now,
1091  rfc1738_username,
1092  rfc1738_passwd);
1093  debug("cmgr: pre-encoded for pub: %s\n", buf);
1094 
1095  safe_free(rfc1738_username);
1096  safe_free(rfc1738_passwd);
1097 
1098  const int encodedLen = base64_encode_len(bufLen);
1099  req->pub_auth = (char *) xmalloc(encodedLen);
1100  struct base64_encode_ctx ctx;
1101  base64_encode_init(&ctx);
1102  size_t blen = base64_encode_update(&ctx, req->pub_auth, bufLen, reinterpret_cast<uint8_t*>(buf));
1103  blen += base64_encode_final(&ctx, req->pub_auth + blen);
1104  req->pub_auth[blen] = '\0';
1105  debug("cmgr: encoded: '%s'\n", req->pub_auth);
1106 }
1107 
1108 static void
1110 {
1111  const char *host_name;
1112  const char *time_str;
1113 
1114  debug("cmgr: decoding pub: '%s'\n", safe_str(req->pub_auth));
1115  safe_free(req->passwd);
1116 
1117  if (!req->pub_auth || strlen(req->pub_auth) < 4 + strlen(safe_str(req->hostname)))
1118  return;
1119 
1120  char *buf = static_cast<char*>(xmalloc(BASE64_DECODE_LENGTH(strlen(req->pub_auth))+1));
1121  struct base64_decode_ctx ctx;
1122  base64_decode_init(&ctx);
1123  size_t decodedLen = 0;
1124  if (!base64_decode_update(&ctx, &decodedLen, reinterpret_cast<uint8_t*>(buf), strlen(req->pub_auth), req->pub_auth) ||
1125  !base64_decode_final(&ctx)) {
1126  debug("cmgr: base64 decode failure. Incomplete auth token string.\n");
1127  xfree(buf);
1128  return;
1129  }
1130  buf[decodedLen] = '\0';
1131 
1132  debug("cmgr: length ok\n");
1133 
1134  /* parse ( a lot of memory leaks, but that is cachemgr style :) */
1135  if ((host_name = strtok(buf, "|")) == nullptr) {
1136  xfree(buf);
1137  return;
1138  }
1139 
1140  debug("cmgr: decoded host: '%s'\n", host_name);
1141 
1142  if ((time_str = strtok(nullptr, "|")) == nullptr) {
1143  xfree(buf);
1144  return;
1145  }
1146 
1147  debug("cmgr: decoded time: '%s' (now: %d)\n", time_str, (int) now);
1148 
1149  char *user_name;
1150  if ((user_name = strtok(nullptr, "|")) == nullptr) {
1151  xfree(buf);
1152  return;
1153  }
1154  rfc1738_unescape(user_name);
1155 
1156  debug("cmgr: decoded uname: '%s'\n", user_name);
1157 
1158  char *passwd;
1159  if ((passwd = strtok(nullptr, "|")) == nullptr) {
1160  xfree(buf);
1161  return;
1162  }
1163  rfc1738_unescape(passwd);
1164 
1165  debug("cmgr: decoded passwd: '%s'\n", passwd);
1166 
1167  /* verify freshness and validity */
1168  if (atoi(time_str) + passwd_ttl < now) {
1169  xfree(buf);
1170  return;
1171  }
1172 
1173  if (strcasecmp(host_name, req->hostname)) {
1174  xfree(buf);
1175  return;
1176  }
1177 
1178  debug("cmgr: verified auth. info.\n");
1179 
1180  /* ok, accept */
1181  safe_free(req->user_name);
1182 
1183  req->user_name = xstrdup(user_name);
1184 
1185  req->passwd = xstrdup(passwd);
1186 
1187  xfree(buf);
1188 }
1189 
1190 static void
1192 {
1193  safe_free(req->passwd);
1194  safe_free(req->pub_auth);
1195 }
1196 
1197 static const char *
1199 {
1200  static char buf[1024];
1201  size_t stringLength = 0;
1202 
1203  if (!req->passwd)
1204  return "";
1205 
1206  int bufLen = snprintf(buf, sizeof(buf), "%s:%s",
1207  req->user_name ? req->user_name : "",
1208  req->passwd);
1209 
1210  int encodedLen = base64_encode_len(bufLen);
1211  if (encodedLen <= 0)
1212  return "";
1213 
1214  char *str64 = static_cast<char *>(xmalloc(encodedLen));
1215  struct base64_encode_ctx ctx;
1216  base64_encode_init(&ctx);
1217  size_t blen = base64_encode_update(&ctx, str64, bufLen, reinterpret_cast<uint8_t*>(buf));
1218  blen += base64_encode_final(&ctx, str64+blen);
1219  str64[blen] = '\0';
1220 
1221  stringLength += snprintf(buf, sizeof(buf), "Authorization: Basic %.*s\r\n", (int)blen, str64);
1222 
1223  assert(stringLength < sizeof(buf));
1224 
1225  snprintf(&buf[stringLength], sizeof(buf) - stringLength, "Proxy-Authorization: Basic %.*s\r\n", (int)blen, str64);
1226 
1227  xfree(str64);
1228  return buf;
1229 }
1230 
1231 static int
1232 check_target_acl(const char *hostname, int port)
1233 {
1234  char config_line[BUFSIZ];
1235  FILE *fp = nullptr;
1236  int ret = 0;
1237  fp = fopen("cachemgr.conf", "r");
1238 
1239  if (fp == nullptr)
1240  fp = fopen(DEFAULT_CACHEMGR_CONFIG, "r");
1241 
1242  if (fp == nullptr) {
1243 #ifdef CACHEMGR_HOSTNAME_DEFINED
1244  // TODO: simplify and maybe get rid of CACHEMGR_HOSTNAME altogether
1245  if (strcmp(hostname, CACHEMGR_HOSTNAME) == 0 && port == CACHE_HTTP_PORT)
1246  return 1;
1247 
1248 #else
1249 
1250  if (strcmp(hostname, "localhost") == 0)
1251  return 1;
1252 
1253  if (strcmp(hostname, getfullhostname()) == 0)
1254  return 1;
1255 
1256 #endif
1257 
1258  return 0;
1259  }
1260 
1261  while (fgets(config_line, BUFSIZ, fp)) {
1262  char *token = nullptr;
1263  strtok(config_line, " \r\n\t");
1264 
1265  if (config_line[0] == '#')
1266  continue;
1267 
1268  if (config_line[0] == '\0')
1269  continue;
1270 
1271  if ((token = strtok(config_line, ":")) == nullptr)
1272  continue;
1273 
1274 #if HAVE_FNMATCH_H
1275 
1276  if (fnmatch(token, hostname, 0) != 0)
1277  continue;
1278 
1279 #else
1280 
1281  if (strcmp(token, hostname) != 0)
1282  continue;
1283 
1284 #endif
1285 
1286  if ((token = strtok(nullptr, ":")) != nullptr) {
1287  int i;
1288 
1289  if (strcmp(token, "*") == 0)
1290 
1291  ; /* Wildcard port specification */
1292  else if (strcmp(token, "any") == 0)
1293 
1294  ; /* Wildcard port specification */
1295  else if (sscanf(token, "%d", &i) != 1)
1296  continue;
1297 
1298  else if (i != port)
1299  continue;
1300  } else if (port != CACHE_HTTP_PORT)
1301  continue;
1302 
1303  ret = 1;
1304 
1305  break;
1306  }
1307 
1308  fclose(fp);
1309  return ret;
1310 }
1311 
static int s_iInitCount
Definition: WinSvc.cc:50
static int Win32SockInit(void)
Definition: WinSvc.cc:930
static void Win32SockCleanup(void)
Definition: WinSvc.cc:981
#define assert(EX)
Definition: assert.h:19
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
void base64_decode_init(struct base64_decode_ctx *ctx)
Definition: base64.c:54
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
int base64_decode_update(struct base64_decode_ctx *ctx, size_t *dst_length, uint8_t *dst, size_t src_length, const char *src)
Definition: base64.c:129
int base64_decode_final(struct base64_decode_ctx *ctx)
Definition: base64.c:159
#define BASE64_DECODE_LENGTH(length)
Definition: base64.h:120
static char server[MAXLINE]
static char * read_post_request(void)
Definition: cachemgr.cc:932
int main(int argc, char *argv[])
Definition: cachemgr.cc:885
static const char * xstrtok(char **str, char del)
Definition: cachemgr.cc:192
static void munge_other_line(MemBuf &out, const char *buf, cachemgr_request *)
Definition: cachemgr.cc:498
static char * menu_url(cachemgr_request *req, const char *action)
Definition: cachemgr.cc:431
static void error_html(const char *msg)
Definition: cachemgr.cc:397
static time_t now
Definition: cachemgr.cc:109
static bool hostname_check(const char *uri)
Definition: cachemgr.cc:220
static const char * script_name
Definition: cachemgr.cc:107
static const char * munge_action_line(const char *_buf, cachemgr_request *req)
Definition: cachemgr.cc:562
static const time_t passwd_ttl
Definition: cachemgr.cc:106
static const char * make_auth_header(const cachemgr_request *req)
Definition: cachemgr.cc:1198
static void decode_pub_auth(cachemgr_request *req)
Definition: cachemgr.cc:1109
static void print_trailer(void)
Definition: cachemgr.cc:235
static void auth_html(const char *host, int port, const char *user_name)
Definition: cachemgr.cc:245
static void reset_auth(cachemgr_request *req)
Definition: cachemgr.cc:1191
static int check_target_acl(const char *hostname, int port)
Definition: cachemgr.cc:1232
static int is_number(const char *str)
Definition: cachemgr.cc:186
static void make_pub_auth(cachemgr_request *req)
Definition: cachemgr.cc:1075
static int parse_status_line(const char *sline, const char **statusStr)
Definition: cachemgr.cc:410
static cachemgr_request * read_request(void)
Definition: cachemgr.cc:989
#define DEFAULT_CACHEMGR_CONFIG
Definition: cachemgr.cc:88
static const char * progname
Definition: cachemgr.cc:108
static void munge_menu_line(MemBuf &out, const char *buf, cachemgr_request *req)
Definition: cachemgr.cc:445
static int read_reply(int s, cachemgr_request *req)
Definition: cachemgr.cc:586
static int process_request(cachemgr_request *req)
Definition: cachemgr.cc:780
static const char * safe_str(const char *str)
Definition: cachemgr.cc:179
static char * read_get_request(void)
Definition: cachemgr.cc:978
optimized set of C chars, with quick membership test and merge support
Definition: CharacterSet.h:18
static const CharacterSet DIGIT
Definition: CharacterSet.h:84
static const CharacterSet ALPHA
Definition: CharacterSet.h:76
static void FreeAddr(struct addrinfo *&ai)
Definition: Address.cc:686
void getAddrInfo(struct addrinfo *&ai, int force=AF_UNSPEC) const
Definition: Address.cc:599
bool isAnyAddr() const
Definition: Address.cc:170
char * toUrl(char *buf, unsigned int len) const
Definition: Address.cc:874
unsigned short port() const
Definition: Address.cc:778
Definition: MemBuf.h:24
virtual void append(const char *c, int sz)
Definition: MemBuf.cc:209
void init(mb_size_t szInit, mb_size_t szMax)
Definition: MemBuf.cc:93
char * buf
Definition: MemBuf.h:134
void appendf(const char *fmt,...) PRINTF_FORMAT_ARG2
Append operation with printf-style arguments.
Definition: Packable.h:61
int debug_enabled
Definition: debug.cc:13
#define BUFSIZ
Definition: defines.h:20
struct _sp sp
static int port
Definition: ldap_backend.cc:70
SQUIDCEXTERN const char * getfullhostname(void)
char * html_quote(const char *)
Definition: html_quote.c:53
#define MAX_IPSTRLEN
Length of buffer that needs to be allocated to old a null-terminated IP-string.
Definition: forward.h:25
const char * FormatRfc1123(time_t)
Definition: rfc1123.cc:202
#define VERSION
#define xfree
#define xstrdup
#define xmalloc
struct hostent * gethostbyname()
static bool action(int fd, size_t metasize, const char *fn, const char *url, const SquidMetaList &meta)
Definition: purge.cc:311
#define rfc1738_escape(x)
Definition: rfc1738.h:52
void rfc1738_unescape(char *url)
Definition: rfc1738.c:146
unsigned short WORD
Definition: smblib-priv.h:145
#define CACHEMGR_HOSTNAME
Definition: squid.h:41
#define CACHE_HTTP_PORT
Definition: squid.h:16
int64_t strtoll(const char *nptr, char **endptr, int base)
Definition: strtoll.c:61
struct sockaddr * ai_addr
socklen_t ai_addrlen
char * user_name
Definition: cachemgr.cc:96
char * processes
Definition: cachemgr.cc:100
char * hostname
Definition: cachemgr.cc:93
char * workers
Definition: cachemgr.cc:99
char * pub_auth
Definition: cachemgr.cc:98
Definition: parse.c:160
static int debug
Definition: tcp-banger3.c:105
char * tempnam(const char *dir, const char *pfx)
Definition: tempnam.c:119
#define NULL
Definition: types.h:166
void * xcalloc(size_t n, size_t sz)
Definition: xalloc.cc:71
#define safe_free(x)
Definition: xalloc.h:73
#define xisspace(x)
Definition: xis.h:17
#define xisdigit(x)
Definition: xis.h:20
const char * xstrerr(int error)
Definition: xstrerror.cc:83
char * xstrndup(const char *s, size_t n)
Definition: xstring.cc:56

 

Introduction

Documentation

Support

Miscellaneous

Web Site Translations

Mirrors