From: rousskov <> Date: Mon, 23 Feb 1998 20:03:01 +0000 (+0000) Subject: - Cache Manager got new Web interface (cachemgr.cgi). New .cgi script forwards X-Git-Tag: SQUID_3_0_PRE1~4021 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7395afb806a3a4c008b8464637ac03974a994359;p=thirdparty%2Fsquid.git - Cache Manager got new Web interface (cachemgr.cgi). New .cgi script forwards basic authentication from browser to squid. Cachemgr.cgi now recognizes "action protection" types described below. - Added better recognition of available protection for actions in Cache Manager. Actions are classified as "public" (no password needed), "protected" (must specify a valid password), "disabled" (those with a "disable" password in squid.conf), and "hidden" (actions that require a password, but do not have corresponding cachemgr_passwd entry in squid.conf). --- diff --git a/src/cache_manager.cc b/src/cache_manager.cc index 804acdcef7..a69448ebc8 100644 --- a/src/cache_manager.cc +++ b/src/cache_manager.cc @@ -1,6 +1,6 @@ /* - * $Id: cache_manager.cc,v 1.5 1998/02/23 03:59:41 rousskov Exp $ + * $Id: cache_manager.cc,v 1.6 1998/02/23 13:03:01 rousskov Exp $ * * DEBUG: section 16 Cache Manager Objects * AUTHOR: Duane Wessels @@ -58,6 +58,7 @@ static void cachemgrParseHeaders(cachemgrStateData *mgr, const request_t *reques static int cachemgrCheckPassword(cachemgrStateData *); static void cachemgrStateFree(cachemgrStateData *mgr); static char *cachemgrPasswdGet(cachemgr_passwd *, const char *); +static const char *cachemgrActionProtection(const action_table *at); static OBJH cachemgrShutdown; static OBJH cachemgrMenu; @@ -102,12 +103,19 @@ cachemgrParseUrl(const char *url) LOCAL_ARRAY(char, password, MAX_URL); action_table *a; cachemgrStateData *mgr = NULL; + const char *prot; t = sscanf(url, "cache_object://%[^/]/%[^@]@%s", host, request, password); if (t < 2) { xstrncpy(request, "menu", MAX_URL); } else if ((a = cachemgrFindAction(request)) == NULL) { debug(16, 0) ("cachemgrParseUrl: action '%s' not found\n", request); return NULL; + } else { + prot = cachemgrActionProtection(a); + if (!strcmp(prot, "disabled") || !strcmp(prot, "hidden")) { + debug(16, 0) ("cachemgrParseUrl: action '%s' is %s\n", request, prot); + return NULL; + } } /* set absent entries to NULL so we can test if they are present later */ mgr = xcalloc(1, sizeof(cachemgrStateData)); @@ -196,7 +204,7 @@ cachemgrStart(int fd, request_t *request, StoreEntry * entry) fd_table[fd].ipaddr, mgr->action); /* get additional info from request headers */ cachemgrParseHeaders(mgr, request); - if (mgr->user_name) + if (mgr->user_name && strlen(mgr->user_name)) debug(16, 1) ("CACHEMGR: %s@%s requesting '%s'\n", mgr->user_name, fd_table[fd].ipaddr, mgr->action); else @@ -269,13 +277,29 @@ cachemgrShutdown(StoreEntry * entryunused) shut_down(0); } +static const char * +cachemgrActionProtection(const action_table *at) +{ + char *pwd; + assert(at); + pwd = cachemgrPasswdGet(Config.passwd_list, at->action); + if (!pwd) + return at->pw_req_flag ? "hidden" : "public"; + if (!strcmp(pwd, "disable")) + return "disabled"; + if (strcmp(pwd, "none") == 0) + return "public"; + return "protected"; +} + static void cachemgrMenu(StoreEntry *sentry) { - action_table *a; - for (a = ActionTable; a != NULL; a = a->next) { - storeAppendPrintf(sentry, " %-22s\t%s\n", a->action, a->desc); - } + action_table *a; + for (a = ActionTable; a != NULL; a = a->next) { + storeAppendPrintf(sentry, " %-22s\t%s\t%s\n", + a->action, a->desc, cachemgrActionProtection(a)); + } } static char * diff --git a/src/cachemgr.cc b/src/cachemgr.cc index f37d0f750c..b2681f55c2 100644 --- a/src/cachemgr.cc +++ b/src/cachemgr.cc @@ -1,5 +1,5 @@ /* - * $Id: cachemgr.cc,v 1.67 1998/02/22 20:40:24 wessels Exp $ + * $Id: cachemgr.cc,v 1.68 1998/02/23 13:03:01 rousskov Exp $ * * DEBUG: section 0 CGI Cache Manager * AUTHOR: Duane Wessels @@ -128,12 +128,26 @@ typedef struct { char *hostname; int port; char *action; + char *user_name; char *passwd; + char *pub_auth; } cachemgr_request; /* - * static variables + * Debugging macros (info goes to error_log on your web server) + * Note: do not run cache manager with non zero debugging level + * if you do not debug, it may write a lot of [sensitive] + * information to your error log. */ + +/* debugging level 0 (disabled) - 3 (max) */ +#define DEBUG_LEVEL 0 +#define debug(level) ((level) > DEBUG_LEVEL || DEBUG_LEVEL <= 0) ? ((void)0) : + +/* + * Static variables and constants + */ +static const time_t passwd_ttl = 60*60*3; /* in sec */ static const char *script_name = "/cgi-bin/cachemgr.cgi"; static const char *const w_space = " \t\n\r"; static const char *progname = NULL; @@ -143,13 +157,48 @@ static struct in_addr no_addr; /* * Function prototypes */ +#define safe_free(str) if (str) { xfree(str); (str) = NULL; } +static const char *safe_str(const char *str); +static char *xstrtok(char **str, char del); static void print_trailer(void); -static void noargs_html(char *host, int port); +static void auth_html(char *host, int port, const char *user_name); static void error_html(const char *msg); +static char *menu_url(cachemgr_request * req, const char *action); +static int parse_status_line(const char *sline, const char **statusStr); static cachemgr_request *read_request(void); static char *read_get_request(void); static char *read_post_request(void); +static void make_pub_auth(cachemgr_request *req); +static void decode_pub_auth(cachemgr_request *req); +static void reset_auth(cachemgr_request *req); +static const char *make_auth_header(const cachemgr_request *req); + + +static const char *safe_str(const char *str) +{ + return str ? str : ""; +} + +static char *xstrtok(char **str, char del) +{ + if (*str) { + char *p = strchr(*str, del); + char *tok = *str; + int len; + if (p) { + *str = p+1; + *p = '\0'; + } else + *str = NULL; + /* trim */ + len = strlen(tok); + while (len && isspace(tok[len-1])) tok[--len] = '\0'; + while (isspace(*tok)) tok++; + return tok; + } else + return ""; +} static void print_trailer(void) @@ -162,23 +211,29 @@ print_trailer(void) } static void -noargs_html(char *host, int port) +auth_html(char *host, int port, const char *user_name) { + if (!user_name) + user_name = ""; printf("Content-type: text/html\r\n\r\n"); printf("Cache Manager Interface\n"); printf("

Cache Manager Interface

\n"); printf("

This is a WWW interface to the instrumentation interface\n"); printf("for the Squid object cache.

\n"); printf("
\n"); - printf("
\n");
     printf("
\n", script_name); - printf("Cache Host:
\n", host); - printf("Cache Port:
\n", port); - printf("
\n"); + printf("\n"); + printf("\n", host ? host : "localhost"); + printf("\n", port); + printf("\n", user_name); + printf("\n"); + printf("
Cache Host:
Cache Port:
Manager name:
Password:

\n"); printf("\n"); - printf("
\n"); + printf("\n"); print_trailer(); } @@ -192,15 +247,34 @@ error_html(const char *msg) print_trailer(); } +/* returns http status extracted from status line or -1 on parsing failure */ +static int +parse_status_line(const char *sline, const char **statusStr) +{ + const char *sp = strchr(sline, ' '); + if (statusStr) + *statusStr = NULL; + if (strncasecmp(sline, "HTTP/", 5) || !sp) + return -1; + while (isspace(*++sp)); + if (!isdigit(*sp)) + return -1; + if (statusStr) + *statusStr = sp; + return atoi(sp); +} + static char * -menu_url(cachemgr_request * req, char *action) +menu_url(cachemgr_request * req, const char *action) { static char url[1024]; - snprintf(url, 1024, "%s?host=%s&port=%d&operation=%s", + snprintf(url, 1024, "%s?host=%s&port=%d&user_name=%s&operation=%s&auth=%s", script_name, req->hostname, req->port, - action); + safe_str(req->user_name), + action, + safe_str(req->pub_auth)); return url; } @@ -208,30 +282,55 @@ static const char * munge_menu_line(const char *buf, cachemgr_request * req) { char *x; - char *a; - char *d; + const char *a; + const char *d; + const char *p; + char *a_url; static char html[1024]; if (strlen(buf) < 1) return buf; if (*buf != ' ') return buf; x = xstrdup(buf); - if ((a = strtok(x, w_space)) == NULL) - return buf; - if ((d = strtok(NULL, "")) == NULL) - return buf; - snprintf(html, 1024, "
  • %s\n", - menu_url(req, a), d); + a = xstrtok(&x, '\t'); + d = xstrtok(&x, '\t'); + p = xstrtok(&x, '\t'); + a_url = xstrdup(menu_url(req, a)); + /* no reason to give a url for a disabled action */ + if (!strcmp(p, "disabled")) + snprintf(html, 1024, "
  • %s (disabled).\n", d, a_url); + else + /* disable a hidden action (requires a password, but password is not in squid.conf) */ + if (!strcmp(p, "hidden")) + snprintf(html, 1024, "
  • %s (hidden).\n", d, a_url); + else + /* disable link if authentication is required and we have no password */ + if (!strcmp(p, "protected") && !req -> passwd) + snprintf(html, 1024, "
  • %s (requires authentication).\n", + d, menu_url(req, "authenticate"), a_url); + else + /* highlight protected but probably available entries */ + if (!strcmp(p, "protected")) + snprintf(html, 1024, "
  • %s\n", + a_url, d); + /* public entry or unknown type of protection */ + else + snprintf(html, 1024, "
  • %s\n", a_url, d); + xfree(a_url); return html; } static int read_reply(int s, cachemgr_request * req) { - char buf[1024]; + char buf[4*1024]; FILE *fp = fdopen(s, "r"); - int state = 0; + /* interpretation states */ + enum { isStatusLine, isHeaders, isBodyStart, isBody, isForward, isEof, isForwardEof, isSuccess, isError } istate = isStatusLine; int parse_menu = 0; + const char *action = req->action; + const char *statusStr = NULL; + int status = -1; if (0 == strlen(req->action)) parse_menu = 1; else if (0 == strcasecmp(req->action, "menu")) @@ -240,29 +339,89 @@ read_reply(int s, cachemgr_request * req) perror("fdopen"); return 1; } - printf("Content-Type: text/html\r\n\r\n"); - if (parse_menu) { - printf("

    Cache Manager menu for %s:

    ", req->hostname); - printf("\n"); - else - printf("\n"); - print_trailer(); + /* printf("\n\n\n
    %s
    \n", req->headers ? req->headers : "no headers"); */ close(s); return 0; } @@ -274,9 +433,9 @@ process_request(cachemgr_request * req) static struct sockaddr_in S; int s; int l; - static char buf[1024]; + static char buf[2*1024]; if (req == NULL) { - noargs_html(CACHEMGR_HOSTNAME, CACHE_HTTP_PORT); + auth_html(CACHEMGR_HOSTNAME, CACHE_HTTP_PORT, ""); return 1; } if (req->hostname == NULL) { @@ -288,6 +447,10 @@ process_request(cachemgr_request * req) if (req->action == NULL) { req->action = xstrdup(""); } + if (!strcmp(req->action, "authenticate")) { + auth_html(req->hostname, req->port, req->user_name); + return 0; + } if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0) { snprintf(buf, 1024, "socket: %s\n", xstrerror()); error_html(buf); @@ -310,13 +473,16 @@ process_request(cachemgr_request * req) error_html(buf); return 1; } - l = snprintf(buf, 1024, + l = snprintf(buf, sizeof(buf), "GET cache_object://%s/%s HTTP/1.0\r\n" "Accept: */*\r\n" + "%s" /* Authentication info or nothing */ "\r\n", req->hostname, - req->action); + req->action, + make_auth_header(req)); write(s, buf, l); + debug(1) fprintf(stderr, "wrote request: '%s'\n", buf); return read_reply(s, req); } @@ -337,6 +503,44 @@ main(int argc, char *argv[]) return process_request(req); } +#if 0 /* left for parts if request headers will ever be processed */ +static char * +read_request_headers() +{ + extern char **environ; + const char **varp = (const char**) environ; + char *buf = NULL; + int size = 0; + if (!varp) + return NULL; + /* not efficient, but simple */ + /* first calc the size */ + for (; *varp; varp++) { + if (1 || !strncasecmp(*varp, "HTTP_", 5)) + size += strlen(*varp); + } + if (!size) + return NULL; + size++; /* paranoid */ + size += 1024; /* @?@?@?@ */ + /* allocate memory */ + buf = calloc(1, size); + /* parse and put headers */ + for (varp = (const char**) environ; *varp; varp++) { + sprintf(buf + strlen(buf), "%s\r\n", *varp); + if (0 && !strncasecmp(*varp, "HTTP_", 5)) { + const char *name = (*varp) + 5; + const char *value = strchr(name, '='); + if (value) { + strncat(buf, name, value-name); + sprintf(buf + strlen(buf), ": %s\r\n", value+1); + } + } + } + return buf; +} +#endif /* left for parts */ + static char * read_post_request(void) { @@ -383,19 +587,123 @@ read_request(void) if (strlen(buf) == 0) return NULL; req = xcalloc(1, sizeof(cachemgr_request)); + /* req->headers = read_request_headers(); */ for (s = strtok(buf, "&"); s != NULL; s = strtok(NULL, "&")) { t = xstrdup(s); if ((q = strchr(t, '=')) == NULL) continue; *q++ = '\0'; - if (0 == strcasecmp(t, "host")) + if (0 == strcasecmp(t, "host") && strlen(q)) req->hostname = xstrdup(q); - if (0 == strcasecmp(t, "port")) + else + if (0 == strcasecmp(t, "port") && strlen(q)) req->port = atoi(q); - if (0 == strcasecmp(t, "password")) + else + if (0 == strcasecmp(t, "user_name") && strlen(q)) + req->user_name = xstrdup(q); + else + if (0 == strcasecmp(t, "passwd") && strlen(q)) req->passwd = xstrdup(q); + else + if (0 == strcasecmp(t, "auth") && strlen(q)) + req->pub_auth = xstrdup(q), decode_pub_auth(req); + else if (0 == strcasecmp(t, "operation")) req->action = xstrdup(q); } + make_pub_auth(req); + debug(1) fprintf(stderr, "cmgr: got req: host: '%s' port: %d uname: '%s' passwd: '%s' auth: '%s' oper: '%s'\n", + safe_str(req->hostname), req->port, safe_str(req->user_name), safe_str(req->passwd), safe_str(req->pub_auth), safe_str(req->action)); return req; } + + +/* Routines to support authentication */ + +/* + * Encodes auth info into a "public" form. + * Currently no powerful encryption is used. + */ +static void +make_pub_auth(cachemgr_request *req) +{ + static char buf[1024]; + safe_free(req->pub_auth); + debug(3) fprintf(stderr, "cmgr: encoding for pub...\n"); + if (!req->passwd && !strlen(req->passwd)) + return; + /* host | time | user | passwd */ + snprintf(buf, sizeof(buf), "%s|%d|%s|%s", + req->hostname, + now, + req->user_name ? req->user_name : "", + req->passwd); + debug(3) fprintf(stderr, "cmgr: pre-encoded for pub: %s\n", buf); + debug(3) fprintf(stderr, "cmgr: encoded: '%s'\n", base64_encode(buf)); + req->pub_auth = xstrdup(base64_encode(buf)); +} + +static void +decode_pub_auth(cachemgr_request *req) +{ + char *buf; + const char *host_name; + const char *time_str; + const char *user_name; + const char *passwd; + + debug(2) fprintf(stderr, "cmgr: decoding pub: '%s'\n", safe_str(req->pub_auth)); + safe_free(req->passwd); + if (!req->pub_auth || strlen(req->pub_auth) < 4 + strlen(safe_str(req->hostname))) + return; + buf = xstrdup(base64_decode(req->pub_auth)); + debug(3) fprintf(stderr, "cmgr: length ok\n"); + /* parse ( a lot of memory leaks, but that is cachemgr style :) */ + if ((host_name = strtok(buf, "|")) == NULL) + return; + debug(3) fprintf(stderr, "cmgr: decoded host: '%s'\n", host_name); + if ((time_str = strtok(NULL, "|")) == NULL) + return; + debug(3) fprintf(stderr, "cmgr: decoded time: '%s' (now: %d)\n", time_str, (int)now); + if ((user_name = strtok(NULL, "|")) == NULL) + return; + debug(3) fprintf(stderr, "cmgr: decoded uname: '%s'\n", user_name); + if ((passwd = strtok(NULL, "|")) == NULL) + return; + debug(2) fprintf(stderr, "cmgr: decoded passwd: '%s'\n", passwd); + /* verify freshness and validity */ + if (atoi(time_str) + passwd_ttl < now) + return; + if (strcasecmp(host_name, req->hostname)) + return; + debug(1) fprintf(stderr, "cmgr: verified auth. info.\n"); + /* ok, accept */ + xfree(req->user_name); + req->user_name = xstrdup(user_name); + req->passwd = xstrdup(passwd); + xfree(buf); +} + +static void +reset_auth(cachemgr_request *req) +{ + safe_free(req->passwd); + safe_free(req->pub_auth); +} + +static const char * +make_auth_header(const cachemgr_request *req) +{ + static char buf[1024]; + const char *str64; + if (!req -> passwd) + return ""; + + snprintf(buf, sizeof(buf), "%s:%s", + req->user_name ? req->user_name : "", + req -> passwd); + + str64 = base64_encode(buf); + snprintf(buf, sizeof(buf), "Authorization: Basic %s\r\n", str64); + return buf; +}