]> git.ipfire.org Git - thirdparty/squid.git/blob - tools/cachemgr.cc
Activate extra compiler checks (#667)
[thirdparty/squid.git] / tools / cachemgr.cc
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 "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 "rfc1123.h"
17 #include "rfc1738.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;
101 } cachemgr_request;
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 = NULL;
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 = NULL;
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",".:[]_") +
223 CharacterSet::ALPHA + CharacterSet::DIGIT;
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
235 print_trailer(void)
236 {
237 printf("<HR noshade size=\"1px\">\n");
238 printf("<ADDRESS>\n");
239 printf("Generated %s, by %s/%s@%s\n",
240 mkrfc1123(now), progname, VERSION, getfullhostname());
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 == NULL)
259 fp = fopen(DEFAULT_CACHEMGR_CONFIG, "r");
260
261 if (fp == NULL)
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 != NULL) {
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")) == NULL)
323 continue;
324
325 if (strchr(server, '*') || strchr(server, '[') || strchr(server, '?')) {
326 need_host = -1;
327 continue;
328 }
329
330 comment = strtok(NULL, "");
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 = NULL;
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,
438 rfc1738_escape(safe_str(req->user_name)),
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
586 read_reply(int s, cachemgr_request * req)
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 = NULL;
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 == NULL) {
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
780 process_request(cachemgr_request * req)
781 {
782
783 char ipbuf[MAX_IPSTRLEN];
784 struct addrinfo *AI = NULL;
785 Ip::Address S;
786 int s;
787 int l;
788
789 static char buf[2 * 1024];
790
791 if (req == NULL) {
792 auth_html(CACHEMGR_HOSTNAME, CACHE_HTTP_PORT, "");
793 return 1;
794 }
795
796 if (req->hostname == NULL) {
797 req->hostname = xstrdup(CACHEMGR_HOSTNAME);
798 }
799
800 if (req->port == 0) {
801 req->port = CACHE_HTTP_PORT;
802 }
803
804 if (req->action == NULL) {
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);
849 Ip::Address::FreeAddr(AI);
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);
857 Ip::Address::FreeAddr(AI);
858 close(s);
859 return 1;
860 }
861
862 Ip::Address::FreeAddr(AI);
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(NULL);
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")) != NULL)
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 *
932 read_post_request(void)
933 {
934 char *s;
935
936 if ((s = getenv("REQUEST_METHOD")) == NULL)
937 return NULL;
938
939 if (0 != strcasecmp(s, "POST"))
940 return NULL;
941
942 if ((s = getenv("CONTENT_LENGTH")) == NULL)
943 return NULL;
944
945 if (*s == '-') // negative length content huh?
946 return NULL;
947
948 uint64_t len;
949
950 char *endptr = s+ strlen(s);
951 if ((len = strtoll(s, &endptr, 10)) <= 0)
952 return NULL;
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 NULL;
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 *
978 read_get_request(void)
979 {
980 char *s;
981
982 if ((s = getenv("QUERY_STRING")) == NULL)
983 return NULL;
984
985 return xstrdup(s);
986 }
987
988 static cachemgr_request *
989 read_request(void)
990 {
991 char *buf;
992
993 cachemgr_request *req;
994 char *s;
995 char *t = NULL;
996 char *q;
997
998 if ((buf = read_post_request()) != NULL)
999 (void) 0;
1000 else if ((buf = read_get_request()) != NULL)
1001 (void) 0;
1002 else
1003 return NULL;
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 NULL;
1015 }
1016
1017 req = (cachemgr_request *)xcalloc(1, sizeof(cachemgr_request));
1018
1019 for (s = strtok(buf, "&"); s != NULL; s = strtok(NULL, "&")) {
1020 safe_free(t);
1021 t = xstrdup(s);
1022
1023 if ((q = strchr(t, '=')) == NULL)
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(NULL, ":")))
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
1075 make_pub_auth(cachemgr_request * req)
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
1109 decode_pub_auth(cachemgr_request * req)
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, "|")) == NULL) {
1136 xfree(buf);
1137 return;
1138 }
1139
1140 debug("cmgr: decoded host: '%s'\n", host_name);
1141
1142 if ((time_str = strtok(NULL, "|")) == NULL) {
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(NULL, "|")) == NULL) {
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(NULL, "|")) == NULL) {
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
1191 reset_auth(cachemgr_request * req)
1192 {
1193 safe_free(req->passwd);
1194 safe_free(req->pub_auth);
1195 }
1196
1197 static const char *
1198 make_auth_header(const cachemgr_request * req)
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 = NULL;
1236 int ret = 0;
1237 fp = fopen("cachemgr.conf", "r");
1238
1239 if (fp == NULL)
1240 fp = fopen(DEFAULT_CACHEMGR_CONFIG, "r");
1241
1242 if (fp == NULL) {
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 = NULL;
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, ":")) == NULL)
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(NULL, ":")) != NULL) {
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