From: Amos Jeffries Date: Fri, 27 Jan 2012 12:33:17 +0000 (-0700) Subject: Cache Manager migration support X-Git-Tag: SQUID_3_2_0_15~15 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a2e5825388d24a3932ce9e7ff6ce84df6927a86f;p=thirdparty%2Fsquid.git Cache Manager migration support * 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. --- diff --git a/src/HttpHeader.cc b/src/HttpHeader.cc index 68a844ec98..00ab7b2079 100644 --- a/src/HttpHeader.cc +++ b/src/HttpHeader.cc @@ -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 }; diff --git a/src/HttpHeader.h b/src/HttpHeader.h index b54970a169..999e8345a2 100644 --- a/src/HttpHeader.h +++ b/src/HttpHeader.h @@ -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 */ diff --git a/src/cache_manager.cc b/src/cache_manager.cc index 1197a7adac..d15f395487 100644 --- a/src/cache_manager.cc +++ b/src/cache_manager.cc @@ -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)); diff --git a/src/err_type.h b/src/err_type.h index e919310c10..a8c28ae7ea 100644 --- a/src/err_type.h +++ b/src/err_type.h @@ -52,6 +52,9 @@ typedef enum { /* ICAP Errors */ ERR_ICAP_FAILURE, + /* Cache Manager */ + MGR_INDEX, + /* Squid problem */ ERR_GATEWAY_FAILURE, diff --git a/src/errorpage.cc b/src/errorpage.cc index 1197445c52..39e10c6c02 100644 --- a/src/errorpage.cc +++ b/src/errorpage.cc @@ -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; } diff --git a/src/mgr/Action.cc b/src/mgr/Action.cc index 44b88a4b3c..83f4cd0adb 100644 --- a/src/mgr/Action.cc +++ b/src/mgr/Action.cc @@ -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 ¶ms = 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); } diff --git a/src/mgr/ActionParams.cc b/src/mgr/ActionParams.cc index 79355ea349..e4c5746820 100644 --- a/src/mgr/ActionParams.cc +++ b/src/mgr/ActionParams.cc @@ -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); diff --git a/src/mgr/ActionParams.h b/src/mgr/ActionParams.h index cb6e3d4bef..71b3fa4f9d 100644 --- a/src/mgr/ActionParams.h +++ b/src/mgr/ActionParams.h @@ -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) diff --git a/src/mgr/BasicActions.cc b/src/mgr/BasicActions.cc index f98a9c7251..1d0a8909a8 100644 --- a/src/mgr/BasicActions.cc +++ b/src/mgr/BasicActions.cc @@ -15,6 +15,23 @@ #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); } diff --git a/src/mgr/BasicActions.h b/src/mgr/BasicActions.h index ffe8008579..495e5bff2b 100644 --- a/src/mgr/BasicActions.h +++ b/src/mgr/BasicActions.h @@ -16,6 +16,22 @@ 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 { diff --git a/tools/cachemgr.cc b/tools/cachemgr.cc index e8c7060b2a..a8c3930ee6 100644 --- a/tools/cachemgr.cc +++ b/tools/cachemgr.cc @@ -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("\n"); printf("Cache Manager Interface\n"); - printf("\n"); + printf("\n"); + + printf("\n"); + + printf("\n"); printf("

Cache Manager Interface

\n"); @@ -290,16 +317,15 @@ auth_html(const char *host, int port, const char *user_name) printf("
\n"); + printf("
\n"); + printf("
\n"); + printf("
\n"); + printf("

CGI Managed Proxies

\n"); printf("
\n", script_name); printf("\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("
Cache Server:
Cache Server:\n"); - printf("\n"); + printf("\n"); + + printf("\n"); print_trailer(); }