]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Cache Manager migration support
authorAmos Jeffries <squid3@treenet.co.nz>
Fri, 27 Jan 2012 12:33:17 +0000 (05:33 -0700)
committerAmos Jeffries <squid3@treenet.co.nz>
Fri, 27 Jan 2012 12:33:17 +0000 (05:33 -0700)
* Add a little bit of XHR script to the CGI cachemgr front page which
probes each of the managed proxies for http:// and https:// capabilities
and produces web links to their internal managers.

* Reserve the template name MGR_INDEX for use by cachemgr scripts.
But do not distribute any preset template. This allows manager apps to
provide their own static template with linked scripts and objects.

* The error page system is updated to create a blanket message
indicating missing template instead of aborting Squid if a template is
not even installed.

Also, add Cross-Origin Request Support for the cache manager API

Now that tools are being implemented to access the cache manager via
http:// scheme we need to accomodate the browser XSS protection
mechanisms which limit XHR based scripts abilities.

This adds CORS headers to manager responses. Permitting XHR to view the
Server header (to detect squid version for known capabilities) and to
flag that the XHR request may need access to credentials for
authenticating with the manager.

This also closes the feature bug 3407 requesting we support the
non-standard "Origin:" header, which is used by the CORS mechanisms.

Future work:
  Support the OPTIONS request used by CORS to detect requirements before
  POSTing. We do not yet use POST in the Squid code so that is left until
  needed.

src/HttpHeader.cc
src/HttpHeader.h
src/cache_manager.cc
src/err_type.h
src/errorpage.cc
src/mgr/Action.cc
src/mgr/ActionParams.cc
src/mgr/ActionParams.h
src/mgr/BasicActions.cc
src/mgr/BasicActions.h
tools/cachemgr.cc

index 68a844ec98fe5051778d5c3531a8c86eb7c616ca..00ab7b20794659c71f22ecfd11189f8ca625a74d 100644 (file)
@@ -114,6 +114,7 @@ static const HttpHeaderFieldAttrs HeadersAttrs[] = {
     {"Max-Forwards", HDR_MAX_FORWARDS, ftInt64},
     {"Mime-Version", HDR_MIME_VERSION, ftStr}, /* for now */
     {"Negotiate", HDR_NEGOTIATE, ftStr},
+    {"Origin", HDR_ORIGIN, ftStr},
     {"Pragma", HDR_PRAGMA, ftStr},
     {"Proxy-Authenticate", HDR_PROXY_AUTHENTICATE, ftStr},
     {"Proxy-Authentication-Info", HDR_PROXY_AUTHENTICATION_INFO, ftStr},
@@ -229,6 +230,7 @@ static http_hdr_type ReplyHeadersArr[] = {
     HDR_ACCEPT_RANGES, HDR_AGE,
     HDR_LOCATION, HDR_MAX_FORWARDS,
     HDR_MIME_VERSION, HDR_PUBLIC, HDR_RETRY_AFTER, HDR_SERVER, HDR_SET_COOKIE, HDR_SET_COOKIE2,
+    HDR_ORIGIN,
     HDR_VARY,
     HDR_WARNING, HDR_PROXY_CONNECTION, HDR_X_CACHE,
     HDR_X_CACHE_LOOKUP,
@@ -247,7 +249,9 @@ static HttpHeaderMask RequestHeadersMask;   /* set run-time using RequestHeaders *
 static http_hdr_type RequestHeadersArr[] = {
     HDR_AUTHORIZATION, HDR_FROM, HDR_HOST,
     HDR_IF_MATCH, HDR_IF_MODIFIED_SINCE, HDR_IF_NONE_MATCH,
-    HDR_IF_RANGE, HDR_MAX_FORWARDS, HDR_PROXY_CONNECTION,
+    HDR_IF_RANGE, HDR_MAX_FORWARDS,
+    HDR_ORIGIN,
+    HDR_PROXY_CONNECTION,
     HDR_PROXY_AUTHORIZATION, HDR_RANGE, HDR_REFERER, HDR_REQUEST_RANGE,
     HDR_USER_AGENT, HDR_X_FORWARDED_FOR, HDR_SURROGATE_CAPABILITY
 };
index b54970a169e567b1cc9ddef853835d974a462e33..999e8345a20d18638da31c56ee2f5f3002042343 100644 (file)
@@ -101,6 +101,7 @@ typedef enum {
     HDR_MIME_VERSION,                   /**< RFC 2626 */
     HDR_NEGOTIATE,                      /**< experimental RFC 2295. Why only this one from 2295? */
     /*HDR_OVERWRITE,*/                  /* RFC 2518 */
+    HDR_ORIGIN,                         /* CORS Draft specification (see http://www.w3.org/TR/cors/) */
     HDR_PRAGMA,                         /**< deprecated RFC 2068,2616 header we may need to erase */
     HDR_PROXY_AUTHENTICATE,             /**< RFC 2608, 2616, 2617 */
     HDR_PROXY_AUTHENTICATION_INFO,      /**< RFC 2617 */
index 1197a7adac63f2975e5c36e761a4252319267f03..d15f3954874fe877b442a09726b7bd271802147b 100644 (file)
@@ -202,8 +202,12 @@ CacheManager::ParseUrl(const char *url)
     if (t < 1) {
         t = sscanf(url, "https://%[^/]/squid-internal-mgr/%[^?]%n?%s", host, request, &pos, params);
     }
-    if (t < 2)
-        xstrncpy(request, "menu", MAX_URL);
+    if (t < 2) {
+        if (strncmp("cache_object://",url,15)==0)
+            xstrncpy(request, "menu", MAX_URL);
+        else
+            xstrncpy(request, "index", MAX_URL);
+    }
 
 #if _SQUID_OS2_
     if (t == 2 && request[0] == '\0') {
@@ -211,7 +215,10 @@ CacheManager::ParseUrl(const char *url)
          * emx's sscanf insists of returning 2 because it sets request
          * to null
          */
-        xstrncpy(request, "menu", MAX_URL);
+        if (strncmp("cache_object://",url,15)==0)
+            xstrncpy(request, "menu", MAX_URL);
+        else
+            xstrncpy(request, "index", MAX_URL);
     }
 #endif
 
@@ -371,6 +378,14 @@ CacheManager::Start(const Comm::ConnectionPointer &client, HttpRequest * request
          */
         rep->header.putAuth("Basic", actionName);
 #endif
+        // Allow cachemgr and other XHR scripts access to our version string
+        if (request->header.has(HDR_ORIGIN)) {
+            rep->header.putExt("Access-Control-Allow-Origin",request->header.getStr(HDR_ORIGIN));
+#if HAVE_AUTH_MODULE_BASIC
+            rep->header.putExt("Access-Control-Allow-Credentials","true");
+#endif
+            rep->header.putExt("Access-Control-Expose-Headers","Server");
+        }
 
         /* store the reply */
         entry->replaceHttpReply(rep);
@@ -382,11 +397,35 @@ CacheManager::Start(const Comm::ConnectionPointer &client, HttpRequest * request
         return;
     }
 
+    if (request->header.has(HDR_ORIGIN)) {
+        cmd->params.httpOrigin = request->header.getStr(HDR_ORIGIN);
+    }
+
     debugs(16, 2, "CacheManager: " <<
            userName << "@" <<
            client << " requesting '" <<
            actionName << "'" );
 
+    // special case: /squid-internal-mgr/ index page
+    if (!strcmp(cmd->profile->name, "index")) {
+        ErrorState err(MGR_INDEX, HTTP_OK, request);
+        err.url = xstrdup(entry->url());
+        HttpReply *rep = err.BuildHttpReply();
+        if (strncmp(rep->body.content(),"Internal Error:", 15) == 0)
+            rep->sline.status = HTTP_NOT_FOUND;
+        // Allow cachemgr and other XHR scripts access to our version string
+        if (request->header.has(HDR_ORIGIN)) {
+            rep->header.putExt("Access-Control-Allow-Origin",request->header.getStr(HDR_ORIGIN));
+#if HAVE_AUTH_MODULE_BASIC
+            rep->header.putExt("Access-Control-Allow-Credentials","true");
+#endif
+            rep->header.putExt("Access-Control-Expose-Headers","Server");
+        }
+        entry->replaceHttpReply(rep);
+        entry->complete();
+        return;
+    }
+
     if (UsingSmp() && IamWorkerProcess()) {
         // is client the right connection to pass here?
         AsyncJob::Start(new Mgr::Forwarder(client, cmd->params, request, entry));
index e919310c108314684e366f0f7c481037e6d5a5c9..a8c28ae7ea70e4a5613800584b51b8221b9db5ed 100644 (file)
@@ -52,6 +52,9 @@ typedef enum {
     /* ICAP Errors */
     ERR_ICAP_FAILURE,
 
+    /* Cache Manager */
+    MGR_INDEX,
+
     /* Squid problem */
     ERR_GATEWAY_FAILURE,
 
index 1197445c52a2054b3cbb3e169d1a2cf7195c48c7..39e10c6c0284c09a350f04f0a7a4814ce51384f8 100644 (file)
@@ -298,8 +298,11 @@ TemplateFile::loadDefault()
     }
 
     /* giving up if failed */
-    if (!loaded())
-        fatal("failed to find or read error text file.");
+    if (!loaded()) {
+        debugs(1, DBG_CRITICAL, "WARNING: failed to find or read error text file " << templateName);
+        parse("Internal Error: Missing Template ", 33, '\0');
+        parse(templateName.termedBuf(), templateName.size(), '\0');
+    }
 
     return true;
 }
index 44b88a4b3cd6aa5f63987b31a86556ed3487a00d..83f4cd0adb6e8dabcc423cc8fa3920e0e032ec6f 100644 (file)
@@ -102,6 +102,15 @@ Mgr::Action::fillEntry(StoreEntry* entry, bool writeHttpHeader)
     if (writeHttpHeader) {
         HttpReply *rep = new HttpReply;
         rep->setHeaders(HTTP_OK, NULL, "text/plain", -1, squid_curtime, squid_curtime);
+        // Allow cachemgr and other XHR scripts access to our version string
+        const ActionParams &params = command().params;
+        if (params.httpOrigin.size() > 0) {
+            rep->header.putExt("Access-Control-Allow-Origin", params.httpOrigin.termedBuf());
+#if HAVE_AUTH_MODULE_BASIC
+            rep->header.putExt("Access-Control-Allow-Credentials","true");
+#endif
+            rep->header.putExt("Access-Control-Expose-Headers","Server");
+        }
         entry->replaceHttpReply(rep);
     }
 
index 79355ea3496d1092b3e094b4fbde47b8d22dc02a..e4c5746820050be7d259a0a5b6dca16a6cee8e7e 100644 (file)
@@ -23,6 +23,7 @@ Mgr::ActionParams::ActionParams(const Ipc::TypedMsgHdr &msg)
     httpMethod = static_cast<_method_t>(m);
 
     msg.getPod(httpFlags);
+    msg.getString(httpOrigin);
 
     msg.getString(actionName);
     msg.getString(userName);
@@ -36,6 +37,7 @@ Mgr::ActionParams::pack(Ipc::TypedMsgHdr &msg) const
     msg.putString(httpUri);
     msg.putInt(httpMethod);
     msg.putPod(httpFlags);
+    msg.putString(httpOrigin);
 
     msg.putString(actionName);
     msg.putString(userName);
index cb6e3d4befaec9c21026b0b54e43355671e7ce66..71b3fa4f9df7217d4327e722bfc009be154fea48 100644 (file)
@@ -29,6 +29,7 @@ public:
     String httpUri; ///< HTTP request URI
     _method_t httpMethod; ///< HTTP request method
     request_flags httpFlags; ///< HTTP request flags
+    String httpOrigin;       ///< HTTP Origin: header (if any)
 
     /* action parameters extracted from the client HTTP request */
     String actionName; ///< action name (and credentials realm)
index f98a9c7251b99a2845bcda36328aba960d53353b..1d0a8909a8b33424b8e32d6f93179ffc6d62eff9 100644 (file)
 #include "Store.h"
 
 
+Mgr::IndexAction::Pointer
+Mgr::IndexAction::Create(const Command::Pointer &cmd)
+{
+    return new IndexAction(cmd);
+}
+
+Mgr::IndexAction::IndexAction(const Command::Pointer &cmd): Action(cmd)
+{
+    debugs(16, 5, HERE);
+}
+
+void
+Mgr::IndexAction::dump(StoreEntry* entry)
+{
+    debugs(16, 5, HERE);
+}
+
 Mgr::MenuAction::Pointer
 Mgr::MenuAction::Create(const Command::Pointer &cmd)
 {
@@ -128,9 +145,10 @@ Mgr::OfflineToggleAction::dump(StoreEntry* entry)
 void
 Mgr::RegisterBasics()
 {
+    RegisterAction("index", "Cache Manager Interface", &Mgr::IndexAction::Create, 0, 1);
+    RegisterAction("menu", "Cache Manager Menu", &Mgr::MenuAction::Create, 0, 1);
     RegisterAction("offline_toggle", "Toggle offline_mode setting", &Mgr::OfflineToggleAction::Create, 1, 1);
     RegisterAction("shutdown", "Shut Down the Squid Process", &Mgr::ShutdownAction::Create, 1, 1);
     RegisterAction("reconfigure", "Reconfigure Squid", &Mgr::ReconfigureAction::Create, 1, 1);
     RegisterAction("rotate", "Rotate Squid Logs", &Mgr::RotateAction::Create, 1, 1);
-    RegisterAction("menu", "Cache Manager Menu", &Mgr::MenuAction::Create, 0, 1);
 }
index ffe8008579671c2cc964a1133f7cc784d210a1d0..495e5bff2bd76881cdb2b857961336c2b6340694 100644 (file)
 namespace Mgr
 {
 
+/// A dummy action placeholder for the no-action requests
+/// a templated Cache Manager index ('home') page.
+/// Display output is produced directly by the receiving worker
+/// without invoking the co-ordinator or action Job.
+class IndexAction: public Action
+{
+public:
+    static Pointer Create(const CommandPointer &cmd);
+    /* Action API */
+    virtual void dump(StoreEntry *entry);
+
+protected:
+    IndexAction(const CommandPointer &cmd);
+};
+
+
 /// returns available Cache Manager actions and their access requirements
 class MenuAction: public Action
 {
index e8c7060b2a29036c6a3bea00f3d22ef997db81d2..a8c3930ee6fd59c783290d521165c79f4a828626 100644 (file)
@@ -274,13 +274,40 @@ auth_html(const char *host, int port, const char *user_name)
     if (!host || !strlen(host))
         host = "";
 
+    fp = fopen("cachemgr.conf", "r");
+
+    if (fp == NULL)
+        fp = fopen(DEFAULT_CACHEMGR_CONFIG, "r");
+
+    if (fp == NULL)
+        printf("X-Error: message=\"Unable to open config %s\"", DEFAULT_CACHEMGR_CONFIG);
+
     printf("Content-Type: text/html\r\n\r\n");
 
     printf("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n");
 
     printf("<HTML><HEAD><TITLE>Cache Manager Interface</TITLE>\n");
 
-    printf("<STYLE type=\"text/css\"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}--></STYLE></HEAD>\n");
+    printf("<STYLE type=\"text/css\"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}--></STYLE>\n");
+
+    printf("<script type=\"text/javascript\">\n");
+    printf("function TS(t, s) {\n");
+    printf(" var x = new XMLHttpRequest();\n");
+    printf(" x.open('GET', 'http' + s + '://' + t + '/squid-internal-mgr/', true);\n");
+    printf(" x.onreadystatechange=function() {\n");
+    printf("  if (x.readyState==4) {\n");
+    printf("   if ((x.status>=200 && x.status <= 299) || x.status==401) {\n");
+    printf("    var v = x.getResponseHeader('Server');\n");
+    printf("    if (v.substring(0,8) == 'squid/3.' && (v[8]=='H' || parseInt(v.substring(8)) >= 2)) {\n");
+    printf("     var d = document.getElementById('H' + s + 'mgr');\n");
+    printf("     if (d.innerHTML == '') d.innerHTML = '<h2>HTTP' + (s=='s'?'S':'') + ' Managed Proxies</h2>';\n");
+    printf("     d.innerHTML = d.innerHTML + '<p>Host: <a href=\"http' + s + '://' + t + '/squid-internal-mgr/\">' + t + '</a></p>';\n");
+    printf(" }}}}\n");
+    printf(" x.send(null);\n");
+    printf("}\n");
+    printf("</script>\n");
+
+    printf("</HEAD>\n");
 
     printf("<BODY><H1>Cache Manager Interface</H1>\n");
 
@@ -290,16 +317,15 @@ auth_html(const char *host, int port, const char *user_name)
 
     printf("<HR noshade size=\"1px\">\n");
 
+    printf("<div id=\"Hsmgr\"></div>\n");
+    printf("<div id=\"Hmgr\"></div>\n");
+    printf("<div id=\"Cmgr\">\n");
+    printf("<h2>CGI Managed Proxies</h2>\n");
     printf("<FORM METHOD=\"POST\" ACTION=\"%s\">\n", script_name);
 
     printf("<TABLE BORDER=\"0\" CELLPADDING=\"10\" CELLSPACING=\"1\">\n");
 
 
-    fp = fopen("cachemgr.conf", "r");
-
-    if (fp == NULL)
-        fp = fopen(DEFAULT_CACHEMGR_CONFIG, "r");
-
     if (fp != NULL) {
         int servers = 0;
         char config_line[BUFSIZ];
@@ -331,9 +357,8 @@ auth_html(const char *host, int port, const char *user_name)
             if (!comment || !*comment)
                 comment = server;
 
-            if (!servers) {
-                printf("<TR><TH ALIGN=\"left\">Cache Server:</TH><TD><SELECT NAME=\"server\">\n");
-            }
+            if (!servers)
+                printf("<TR><TH ALIGN=\"left\">Cache Server:</TH><TD><SELECT id=\"server\" NAME=\"server\">\n");
 
             printf("<OPTION VALUE=\"%s\"%s>%s</OPTION>\n", server, (servers || *host) ? "" : " SELECTED", comment);
             servers++;
@@ -377,7 +402,14 @@ auth_html(const char *host, int port, const char *user_name)
 
     printf("<INPUT TYPE=\"submit\" VALUE=\"Continue...\">\n");
 
-    printf("</FORM>\n");
+    printf("</FORM></div>\n");
+
+    printf("<script type=\"text/javascript\">\n");
+    printf("var s = document.getElementById(\"server\");\n");
+    printf("for (var i = 0; i < s.childElementCount; i++) {\n");
+    printf(" TS(s.children[i].value, '');\n");
+    printf(" TS(s.children[i].value, 's');\n");
+    printf("}</script>\n");
 
     print_trailer();
 }