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