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