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