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