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