/** * \brief Add an URL policy file at the given URL * * Adds an URL policy file at the given URL to the list of managed URL policy files. * Policy files aren't loaded when adding, this is delayed until the policy file is actually needed. * Waits for mutex at start and releases mutex when finished * \param url The URL where the URL policy file resides * \return A pointer to the newly created URLPolicyFile object */ URLPolicyFile* SecurityManager::addURLPolicyFile(const URLInfo& url) { RecMutex::Lock l(mutex); URLPolicyFile* file = new URLPolicyFile(url); if(file->isValid()) { LOG(LOG_INFO, _("SECURITY: Added URL policy file is valid, adding to URL policy file list (") << url << ")"); pendingURLPFiles.insert(URLPFilePair(url.getHostname(), file)); } return file; }
/** * \brief Add an URL policy file at the given URL * * Adds an URL policy file at the given URL to the list of managed URL policy files. * Policy files aren't loaded when adding, this is delayed until the policy file is actually needed. * Waits for mutex at start and releases mutex when finished * \param url The URL where the URL policy file resides * \return A pointer to the newly created URLPolicyFile object */ URLPolicyFile* SecurityManager::addURLPolicyFile(const URLInfo& url) { sem_wait(&mutex); //-- Lock acquired URLPolicyFile* file = new URLPolicyFile(url); if(file->isValid()) { LOG(LOG_NO_INFO, _("SECURITY: Added URL policy file is valid, adding to URL policy file list (") << url << ")"); pendingURLPFiles.insert(URLPFilePair(url.getHostname(), file)); } //++ Release lock sem_post(&mutex); return file; }
/** * \brief Loads and parses an URLPolicy file * * Can only be called from within SecurityManager * Waits for mutex at start and releases mutex when finished * \see SecurityManager::loadURLPolicyFile() */ void URLPolicyFile::load() { //TODO: support download timeout handling //Invalid URLPolicyFile or already loaded, ignore this call if(!isValid() || isLoaded()) return; //We only try loading once, if something goes wrong, valid will be set to 'invalid' loaded = true; URLPolicyFile* master = getMasterPolicyFile(); Mutex::Lock l(mutex); //Check if this file is allowed/ignored by the master policy file if(!isMaster()) { //Load master policy file if not loaded yet getSys()->securityManager->loadPolicyFile(master); //Master policy file found and valid and has a site-control entry if(master->isValid() && master->getSiteControl() != NULL) { PolicySiteControl::METAPOLICY permittedPolicies = master->getSiteControl()->getPermittedPolicies(); //For all types: master-only, none if(permittedPolicies == PolicySiteControl::MASTER_ONLY || permittedPolicies == PolicySiteControl::NONE) ignore = true; //Only for FTP: by-ftp-filename else if(subtype == FTP && permittedPolicies == PolicySiteControl::BY_FTP_FILENAME && url.getPathFile() != "crossdomain.xml") ignore = true; } } //No caching needed for this download, we don't expect very big files Downloader* downloader=getSys()->downloadManager->download(url, false, NULL); //Wait until the file is fetched downloader->waitForTermination(); if(downloader->hasFailed()) valid = false; //If files are redirected, we use the new URL as the file's URL if(isValid() && downloader->isRedirected()) { URLInfo newURL(downloader->getURL()); if(url.getHostname() != newURL.getHostname()) { LOG(LOG_INFO, _("SECURITY: Policy file was redirected to other domain, marking invalid")); valid = false; } url = newURL; LOG(LOG_INFO, _("SECURITY: Policy file was redirected")); } //Policy files must have on of the following content-types to be valid: //text/*, application/xml or application/xhtml+xml tiny_string contentType = downloader->getHeader("content-type"); if(isValid() && (subtype == HTTP || subtype == HTTPS) && contentType.substr(0, 5) != "text/" && contentType != "application/xml" && contentType != "application/xhtml+xml") { LOG(LOG_INFO, _("SECURITY: Policy file has an invalid content-type, marking invalid")); valid = false; } //One more check from the master file: see if the content-type is OK //if site-control specifies by-content-type if(isValid() && !isMaster()) { //If the site-control policy of the master policy file is by-content-type, only policy files with //content-type = text/x-cross-domain-policy are allowed. if(master->isValid() && master->getSiteControl() != NULL && (subtype == HTTP || subtype == HTTPS) && master->getSiteControl()->getPermittedPolicies() == PolicySiteControl::BY_CONTENT_TYPE && contentType != "text/x-cross-domain-policy") { LOG(LOG_INFO, _("SECURITY: Policy file content-type isn't strict, marking invalid")); ignore = true; } } //We've checked the master file to see of we need to ignore this file. (not the case) //Now let's parse this file. A HTTP 404 results in a failed download. if(isValid() && !isIgnored()) { istream s(downloader); size_t bufLength = downloader->getLength(); uint8_t* buf=new uint8_t[bufLength]; //TODO: avoid this useless copy s.read((char*)buf,bufLength); //We're done with the downloader, lets destroy ASAP getSys()->downloadManager->destroy(downloader); CrossDomainPolicy::POLICYFILESUBTYPE parserSubtype = CrossDomainPolicy::NONE; if(subtype == HTTP) parserSubtype = CrossDomainPolicy::HTTP; else if(subtype == HTTPS) parserSubtype = CrossDomainPolicy::HTTPS; else if(subtype == FTP) parserSubtype = CrossDomainPolicy::FTP; CrossDomainPolicy parser(buf, bufLength, CrossDomainPolicy::URL, parserSubtype, isMaster()); CrossDomainPolicy::ELEMENT elementType = parser.getNextElement(); while(elementType != CrossDomainPolicy::END && elementType != CrossDomainPolicy::INVALID) { if(elementType == CrossDomainPolicy::SITE_CONTROL) { siteControl = new PolicySiteControl(this, parser.getPermittedPolicies()); //No more parsing is needed if this site-control entry specifies that //no policy files are allowed if(isMaster() && siteControl->getPermittedPolicies() == PolicySiteControl::NONE) break; } else if(elementType == CrossDomainPolicy::ALLOW_ACCESS_FROM) //URL policy files don't use to-ports allowAccessFrom.push_back(new PolicyAllowAccessFrom(this, parser.getDomain(), "", parser.getSecure(), parser.getSecureSpecified())); else if(elementType == CrossDomainPolicy::ALLOW_HTTP_REQUEST_HEADERS_FROM) allowHTTPRequestHeadersFrom.push_back(new PolicyAllowHTTPRequestHeadersFrom(this, parser.getDomain(), parser.getHeaders(), parser.getSecure(), parser.getSecureSpecified())); elementType = parser.getNextElement(); } delete[] buf; //The last element was INVALID if(elementType == CrossDomainPolicy::INVALID) valid = false; if(isMaster()) { //Set siteControl to the default value if it isn't set before and we are a master file if(siteControl == NULL) siteControl = new PolicySiteControl(this, ""); //Ignore this file if the site control policy is "none" if(siteControl->getPermittedPolicies() == PolicySiteControl::NONE) ignore = true; //by-ftp-filename only applies to FTP if((subtype == HTTP || subtype == HTTPS) && siteControl->getPermittedPolicies() == PolicySiteControl::BY_FTP_FILENAME) valid = false; //by-content-type only applies to HTTP(S) else if(subtype == FTP && siteControl->getPermittedPolicies() == PolicySiteControl::BY_CONTENT_TYPE) valid = false; } } else { //Failed to download the file, marking this file as invalid valid = false; getSys()->downloadManager->destroy(downloader); } }
/** * \brief Search for URL policy files relevant to a given URL * * Searches the loaded URL policy file list for URL policy files that are relevant to a given URL. * If \c loadPendingPolicies is true, it search the pending URL policy files list next, * loading every relative policy file. * Waits for mutex at start and releases mutex when finished * \param url The URL that will be evaluated using the relevant policy files. * \param loadPendingPolicies Whether or not to load (and thus check) pending URL policy files. * \return An pointer to a newly created URLPFileList containing the relevant policy files. * This list needs to be deleted after use. */ URLPFileList* SecurityManager::searchURLPolicyFiles(const URLInfo& url, bool loadPendingPolicies) { URLPFileList* result = new URLPFileList; //Get or create the master policy file object URLInfo masterURL = url.goToURL("/crossdomain.xml"); URLPolicyFile* master = getURLPolicyFileByURL(masterURL); if(master == NULL) master = addURLPolicyFile(masterURL); if(loadPendingPolicies) getSys()->securityManager->loadPolicyFile(master); RecMutex::Lock l(mutex); //Check if the master policy file is loaded. //If another user-added relevant policy file is already loaded, //it's master will have already been loaded too (to check if it is allowed). //So IF any relevant policy file is loaded already, then the master will be too. if(master->isLoaded() && master->isValid()) { LOG(LOG_INFO, _("SECURITY: Master policy file is loaded and valid (") << url << ")"); PolicySiteControl::METAPOLICY siteControl = master->getSiteControl()->getPermittedPolicies(); //Master defines no policy files are allowed at all if(siteControl == PolicySiteControl::NONE) { LOG(LOG_INFO, _("SECURITY: DISALLOWED: Master policy file disallows policy files")); return NULL; } result->push_back(master); //Non-master policy files are allowed if(siteControl != PolicySiteControl::MASTER_ONLY) { LOG(LOG_INFO, _("SECURITY: Searching for loaded non-master policy files (") << loadedURLPFiles.count(url.getHostname()) << ")"); URLPFileMapConstItPair range = loadedURLPFiles.equal_range(url.getHostname()); URLPFileMapConstIt i = range.first; for(;i != range.second; ++i) { if((*i).second == master) continue; result->push_back((*i).second); } //And check the pending policy files next (if we are allowed to) if(loadPendingPolicies) { LOG(LOG_INFO, _("SECURITY: Searching for and loading pending non-master policy files (") << pendingURLPFiles.count(url.getHostname()) << ")"); while(true) { i=pendingURLPFiles.find(url.getHostname()); if(i==pendingURLPFiles.end()) break; result->push_back((*i).second); l.release(); getSys()->securityManager->loadPolicyFile((*i).second); //NOTE: loadPolicyFile() will change pendingURLPFiles, //erasing & moving to loadURLPFiles. Therefore, the //iterator i is now invalid. l.acquire(); } } } } return result; }