Configurable SSL error details messages This project adds support for a translatable and customisable error detail file (errors/templates/error_details.txt). The file is stored like we store error page templates today. Inside the file, an HTTP-like format used that can be later extended to other error details (and beyond): name: value details: "value" descr: "value" or name: value details: "multi line value" descr: "value with a \"quoted string\" inside" The code supports future translations, just like Squid already support error page translations. Some Technical details: - The errorpage code which is related to loading and parsing error templates moved to TemplateFile class. This class is used as base class for ErrorPageFile class which used to load error page templates. - The HttpHeader parser used to parse error details - The error details for various languages cached to memory - The ErrorDetailsList used to store a list of error details for a language/locale - The ErrorDetailsManager is a class used to load and manage multiple error details list (ErrorDetailsList objects) for many languages. It also implements a simple cache. === added file 'errors/templates/error-details.txt' --- errors/templates/error-details.txt 1970-01-01 00:00:00 +0000 +++ errors/templates/error-details.txt 2011-05-20 13:09:31 +0000 @@ -0,0 +1,127 @@ +name: X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT +detail: "SSL Certficate error: certificate issuer (CA) not known: %ssl_ca_name" +descr: "Unable to get issuer certificate" + +name: X509_V_ERR_UNABLE_TO_GET_CRL +detail: "%ssl_error_descr: %ssl_subject" +descr: "Unable to get certificate CRL" + +name: X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE +detail: "%ssl_error_descr: %ssl_subject" +descr: "Unable to decrypt certificate's signature" + +name: X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE +detail: "%ssl_error_descr: %ssl_subject" +descr: "Unable to decrypt CRL's signature" + +name: X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY +detail: "Unable to decode issuer (CA) public key: %ssl_ca_name" +descr: "Unable to decode issuer public key" + +name: X509_V_ERR_CERT_SIGNATURE_FAILURE +detail: "%ssl_error_descr: %ssl_subject" +descr: "Certificate signature failure" + +name: X509_V_ERR_CRL_SIGNATURE_FAILURE +detail: "%ssl_error_descr: %ssl_subject" +descr: "CRL signature failure" + +name: X509_V_ERR_CERT_NOT_YET_VALID +detail: "SSL Certficate is not valid before: %ssl_notbefore" +descr: "Certificate is not yet valid" + +name: X509_V_ERR_CERT_HAS_EXPIRED +detail: "SSL Certificate expired on: %ssl_notafter" +descr: "Certificate has expired" + +name: X509_V_ERR_CRL_NOT_YET_VALID +detail: "%ssl_error_descr: %ssl_subject" +descr: "CRL is not yet valid" + +name: X509_V_ERR_CRL_HAS_EXPIRED +detail: "%ssl_error_descr: %ssl_subject" +descr: "CRL has expired" + +name: X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD +detail: "SSL Certificate has invalid start date (the 'not before' field): %ssl_subject" +descr: "Format error in certificate's notBefore field" + +name: X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD +detail: "SSL Certificate has invalid expiration date (the 'not after' field): %ssl_subject" +descr: "Format error in certificate's notAfter field" + +name: X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD +detail: "%ssl_error_descr: %ssl_subject" +descr: "Format error in CRL's lastUpdate field" + +name: X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD +detail: "%ssl_error_descr: %ssl_subject" +descr: "Format error in CRL's nextUpdate field" + +name: X509_V_ERR_OUT_OF_MEM +detail: "%ssl_error_descr" +descr: "Out of memory" + +name: X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT +detail: "Self-signed SSL Certificate: %ssl_subject" +descr: "Self signed certificate" + +name: X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN +detail: "Self-signed SSL Certificate in chain: %ssl_subject" +descr: "Self signed certificate in certificate chain" + +name: X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY +detail: "SSL Certficate error: certificate issuer (CA) not known: %ssl_ca_name" +descr: "Unable to get local issuer certificate" + +name: X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE +detail: "%ssl_error_descr: %ssl_subject" +descr: "Unable to verify the first certificate" + +name: X509_V_ERR_CERT_CHAIN_TOO_LONG +detail: "%ssl_error_descr: %ssl_subject" +descr: "Certificate chain too long" + +name: X509_V_ERR_CERT_REVOKED +detail: "%ssl_error_descr: %ssl_subject" +descr: "Certificate revoked" + +name: X509_V_ERR_INVALID_CA +detail: "%ssl_error_descr: %ssl_ca_name" +descr: "Invalid CA certificate" + +name: X509_V_ERR_PATH_LENGTH_EXCEEDED +detail: "%ssl_error_descr: %ssl_subject" +descr: "Path length constraint exceeded" + +name: X509_V_ERR_INVALID_PURPOSE +detail: "%ssl_error_descr: %ssl_subject" +descr: "Unsupported certificate purpose" + +name: X509_V_ERR_CERT_UNTRUSTED +detail: "%ssl_error_descr: %ssl_subject" +descr: "Certificate not trusted" + +name: X509_V_ERR_CERT_REJECTED +detail: "%ssl_error_descr: %ssl_subject" +descr: "Certificate rejected" + +name: X509_V_ERR_SUBJECT_ISSUER_MISMATCH +detail: "%ssl_error_descr: %ssl_ca_name" +descr: "Subject issuer mismatch" + +name: X509_V_ERR_AKID_SKID_MISMATCH +detail: "%ssl_error_descr: %ssl_subject" +descr: "Authority and subject key identifier mismatch" + +name: X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH +detail: "%ssl_error_descr: %ssl_ca_name" +descr: "Authority and issuer serial number mismatch" + +name: X509_V_ERR_KEYUSAGE_NO_CERTSIGN +detail: "%ssl_error_descr: %ssl_subject" +descr: "Key usage does not include certificate signing" + +name: X509_V_ERR_APPLICATION_VERIFICATION +detail: "%ssl_error_descr: %ssl_subject" +descr: "Application verification failure" === modified file 'src/HttpHeader.cc' --- src/HttpHeader.cc 2011-05-02 01:14:30 +0000 +++ src/HttpHeader.cc 2011-05-20 21:09:56 +0000 @@ -368,109 +368,111 @@ assert(label); memset(hs, 0, sizeof(HttpHeaderStat)); hs->label = label; statHistEnumInit(&hs->hdrUCountDistr, 32); /* not a real enum */ statHistEnumInit(&hs->fieldTypeDistr, HDR_ENUM_END); statHistEnumInit(&hs->ccTypeDistr, CC_ENUM_END); statHistEnumInit(&hs->scTypeDistr, SC_ENUM_END); } /* * HttpHeader Implementation */ HttpHeader::HttpHeader() : owner (hoNone), len (0) { httpHeaderMaskInit(&mask, 0); } HttpHeader::HttpHeader(const http_hdr_owner_type anOwner): owner(anOwner), len(0) { - assert(anOwner > hoNone && anOwner <= hoReply); + assert(anOwner > hoNone && anOwner < hoEnd); debugs(55, 7, "init-ing hdr: " << this << " owner: " << owner); httpHeaderMaskInit(&mask, 0); } HttpHeader::HttpHeader(const HttpHeader &other): owner(other.owner), len(other.len) { httpHeaderMaskInit(&mask, 0); update(&other, NULL); // will update the mask as well } HttpHeader::~HttpHeader() { clean(); } HttpHeader & HttpHeader::operator =(const HttpHeader &other) { if (this != &other) { // we do not really care, but the caller probably does assert(owner == other.owner); clean(); update(&other, NULL); // will update the mask as well len = other.len; } return *this; } void HttpHeader::clean() { HttpHeaderPos pos = HttpHeaderInitPos; HttpHeaderEntry *e; - assert(owner > hoNone && owner <= hoReply); + assert(owner > hoNone && owner < hoEnd); debugs(55, 7, "cleaning hdr: " << this << " owner: " << owner); PROF_start(HttpHeaderClean); /* * An unfortunate bug. The entries array is initialized * such that count is set to zero. httpHeaderClean() seems to * be called both when 'hdr' is created, and destroyed. Thus, * we accumulate a large number of zero counts for 'hdr' before * it is ever used. Can't think of a good way to fix it, except * adding a state variable that indicates whether or not 'hdr' * has been used. As a hack, just never count zero-sized header * arrays. */ - if (0 != entries.count) - statHistCount(&HttpHeaderStats[owner].hdrUCountDistr, entries.count); + if (owner <= hoReply) { + if (0 != entries.count) + statHistCount(&HttpHeaderStats[owner].hdrUCountDistr, entries.count); - HttpHeaderStats[owner].destroyedCount++; + HttpHeaderStats[owner].destroyedCount++; - HttpHeaderStats[owner].busyDestroyedCount += entries.count > 0; + HttpHeaderStats[owner].busyDestroyedCount += entries.count > 0; - while ((e = getEntry(&pos))) { - /* tmp hack to try to avoid coredumps */ + while ((e = getEntry(&pos))) { + /* tmp hack to try to avoid coredumps */ - if (e->id < 0 || e->id >= HDR_ENUM_END) { - debugs(55, 0, "HttpHeader::clean BUG: entry[" << pos << "] is invalid (" << e->id << "). Ignored."); - } else { - statHistCount(&HttpHeaderStats[owner].fieldTypeDistr, e->id); - /* yes, this deletion leaves us in an inconsistent state */ - delete e; + if (e->id < 0 || e->id >= HDR_ENUM_END) { + debugs(55, 0, "HttpHeader::clean BUG: entry[" << pos << "] is invalid (" << e->id << "). Ignored."); + } else { + statHistCount(&HttpHeaderStats[owner].fieldTypeDistr, e->id); + /* yes, this deletion leaves us in an inconsistent state */ + delete e; + } } - } + } // if (owner <= hoReply) entries.clean(); httpHeaderMaskInit(&mask, 0); len = 0; PROF_stop(HttpHeaderClean); } /* append entries (also see httpHeaderUpdate) */ void HttpHeader::append(const HttpHeader * src) { const HttpHeaderEntry *e; HttpHeaderPos pos = HttpHeaderInitPos; assert(src); assert(src != this); debugs(55, 7, "appending hdr: " << this << " += " << src); while ((e = src->getEntry(&pos))) { addEntry(e->clone()); } } === modified file 'src/HttpHeader.h' --- src/HttpHeader.h 2011-02-21 04:32:39 +0000 +++ src/HttpHeader.h 2011-05-20 21:09:37 +0000 @@ -140,41 +140,45 @@ ftInvalid = HDR_ENUM_END, /**< to catch nasty errors with hdr_id<->fld_type clashes */ ftInt, ftInt64, ftStr, ftDate_1123, ftETag, ftPCc, ftPContRange, ftPRange, ftPSc, ftDate_1123_or_ETag } field_type; /** Possible owners of http header */ typedef enum { hoNone =0, #if USE_HTCP hoHtcpReply, #endif hoRequest, - hoReply + hoReply, +#if USE_SSL + hoErrorDetail, +#endif + hoEnd } http_hdr_owner_type; struct _HttpHeaderFieldAttrs { const char *name; http_hdr_type id; field_type type; }; /** Iteration for headers; use HttpHeaderPos as opaque type, do not interpret */ typedef ssize_t HttpHeaderPos; /* use this and only this to initialize HttpHeaderPos */ #define HttpHeaderInitPos (-1) /* these two are defined in structs.h */ /// \todo CLEANUP: Kill this. typedef struct _TimeOrTag TimeOrTag; === modified file 'src/errorpage.cc' --- src/errorpage.cc 2011-05-14 06:11:27 +0000 +++ src/errorpage.cc 2011-05-20 21:32:14 +0000 @@ -111,235 +111,359 @@ } }; /// \ingroup ErrorPageInternal static Vector ErrorDynamicPages; /* local prototypes */ /// \ingroup ErrorPageInternal static const int error_hard_text_count = sizeof(error_hard_text) / sizeof(*error_hard_text); /// \ingroup ErrorPageInternal static char **error_text = NULL; /// \ingroup ErrorPageInternal static int error_page_count = 0; /// \ingroup ErrorPageInternal static MemBuf error_stylesheet; -static char *errorTryLoadText(const char *page_name, const char *dir, bool silent = false); -static char *errorLoadText(const char *page_name); static const char *errorFindHardText(err_type type); static ErrorDynamicPageInfo *errorDynamicPageInfoCreate(int id, const char *page_name); static void errorDynamicPageInfoDestroy(ErrorDynamicPageInfo * info); static IOCB errorSendComplete; +/// \ingroup ErrorPageInternal +/// manages an error page template +class ErrorPageFile: public TemplateFile { +public: + ErrorPageFile(const char *name): TemplateFile(name) { textBuf.init();} + + /// The template text data read from disk + const char *text() { return textBuf.content(); } + +private: + /// stores the data read from disk to a local buffer + virtual bool parse(const char *buf, int len, bool eof) { + if (len) + textBuf.append(buf, len); + return true; + } + + MemBuf textBuf; ///< A buffer to store the error page +}; /// \ingroup ErrorPageInternal err_type &operator++ (err_type &anErr) { int tmp = (int)anErr; anErr = (err_type)(++tmp); return anErr; } /// \ingroup ErrorPageInternal int operator - (err_type const &anErr, err_type const &anErr2) { return (int)anErr - (int)anErr2; } void errorInitialize(void) { err_type i; const char *text; error_page_count = ERR_MAX + ErrorDynamicPages.size(); error_text = static_cast(xcalloc(error_page_count, sizeof(char *))); for (i = ERR_NONE, ++i; i < error_page_count; ++i) { safe_free(error_text[i]); if ((text = errorFindHardText(i))) { /**\par * Index any hard-coded error text into defaults. */ error_text[i] = xstrdup(text); } else if (i < ERR_MAX) { /**\par * Index precompiled fixed template files from one of two sources: * (a) default language translation directory (error_default_language) * (b) admin specified custom directory (error_directory) */ - error_text[i] = errorLoadText(err_type_str[i]); - + ErrorPageFile errTmpl(err_type_str[i]); + error_text[i] = errTmpl.loadDefault() ? xstrdup(errTmpl.text()) : NULL; } else { /** \par * Index any unknown file names used by deny_info. */ ErrorDynamicPageInfo *info = ErrorDynamicPages.items[i - ERR_MAX]; assert(info && info->id == i && info->page_name); const char *pg = info->page_name; if (info->page_redirect != HTTP_STATUS_NONE) pg = info->page_name +4; if (strchr(pg, ':') == NULL) { /** But only if they are not redirection URL. */ - error_text[i] = errorLoadText(pg); + ErrorPageFile errTmpl(pg); + error_text[i] = errTmpl.loadDefault() ? xstrdup(errTmpl.text()) : NULL; } } } error_stylesheet.reset(); // look for and load stylesheet into global MemBuf for it. if (Config.errorStylesheet) { - char *temp = errorTryLoadText(Config.errorStylesheet,NULL); - if (temp) { - error_stylesheet.Printf("%s",temp); - safe_free(temp); - } + ErrorPageFile tmpl("StylesSheet"); + tmpl.loadFromFile(Config.errorStylesheet); + error_stylesheet.Printf("%s",tmpl.text()); } + + Ssl::errorDetailInitialize(); } void errorClean(void) { if (error_text) { int i; for (i = ERR_NONE + 1; i < error_page_count; i++) safe_free(error_text[i]); safe_free(error_text); } while (ErrorDynamicPages.size()) errorDynamicPageInfoDestroy(ErrorDynamicPages.pop_back()); error_page_count = 0; + + Ssl::errorDetailClean(); } /// \ingroup ErrorPageInternal static const char * errorFindHardText(err_type type) { int i; for (i = 0; i < error_hard_text_count; i++) if (error_hard_text[i].type == type) return error_hard_text[i].text; return NULL; } -/** - * \ingroup ErrorPageInternal - * - * Load into the in-memory error text Index a file probably available at: - * (a) admin specified custom directory (error_directory) - * (b) default language translation directory (error_default_language) - * (c) English sub-directory where errors should ALWAYS exist - */ -static char * -errorLoadText(const char *page_name) +TemplateFile::TemplateFile(const char *name): silent(false), wasLoaded(false), templateName(name) +{ + assert(name); +} + +bool +TemplateFile::loadDefault() { - char *text = NULL; + if (loaded()) // already loaded? + return true; /** test error_directory configured location */ - if (Config.errorDirectory) - text = errorTryLoadText(page_name, Config.errorDirectory); + if (Config.errorDirectory) { + char path[MAXPATHLEN]; + snprintf(path, sizeof(path), "%s/%s", Config.errorDirectory, templateName.termedBuf()); + loadFromFile(path); + } #if USE_ERR_LOCALES /** test error_default_language location */ - if (!text && Config.errorDefaultLanguage) { - char dir[256]; - snprintf(dir,256,"%s/%s", DEFAULT_SQUID_ERROR_DIR, Config.errorDefaultLanguage); - text = errorTryLoadText(page_name, dir); - if (!text) { + if (!loaded() && Config.errorDefaultLanguage) { + if (!tryLoadTemplate(Config.errorDefaultLanguage)) { debugs(1, DBG_CRITICAL, "Unable to load default error language files. Reset to backups."); } } #endif /* test default location if failed (templates == English translation base templates) */ - if (!text) { - text = errorTryLoadText(page_name, DEFAULT_SQUID_ERROR_DIR"/templates"); + if (!loaded()) { + tryLoadTemplate("templates"); } /* giving up if failed */ - if (!text) + if (!loaded()) fatal("failed to find or read error text file."); - return text; + return true; } -/// \ingroup ErrorPageInternal -static char * -errorTryLoadText(const char *page_name, const char *dir, bool silent) +bool +TemplateFile::tryLoadTemplate(const char *lang) { - int fd; + assert(lang); + char path[MAXPATHLEN]; + /* TODO: prep the directory path string to prevent snprintf ... */ + snprintf(path, sizeof(path), "%s/%s/%s", + DEFAULT_SQUID_ERROR_DIR, lang, templateName.termedBuf()); + path[MAXPATHLEN-1] = '\0'; + + if (loadFromFile(path)) + return true; + +#if HAVE_GLOB + if ( strlen(lang) == 2) { + /* TODO glob the error directory for sub-dirs matching: '-*' */ + /* use first result. */ + debugs(4,2, HERE << "wildcard fallback errors not coded yet."); + } +#endif + + return false; +} + +bool +TemplateFile::loadFromFile(const char *path) +{ + int fd; char buf[4096]; - char *text; ssize_t len; - MemBuf textbuf; - // maybe received compound parts, maybe an absolute page_name and no dir - if (dir) - snprintf(path, sizeof(path), "%s/%s", dir, page_name); - else - snprintf(path, sizeof(path), "%s", page_name); + if (loaded()) // already loaded? + return true; fd = file_open(path, O_RDONLY | O_TEXT); if (fd < 0) { /* with dynamic locale negotiation we may see some failures before a success. */ if (!silent) debugs(4, DBG_CRITICAL, HERE << "'" << path << "': " << xstrerror()); - return NULL; + wasLoaded = false; + return wasLoaded; } - textbuf.init(); - while ((len = FD_READ_METHOD(fd, buf, sizeof(buf))) > 0) { - textbuf.append(buf, len); + if (!parse(buf, len, false)) { + debugs(4, DBG_CRITICAL, HERE << " parse error while reading template file: " << path); + wasLoaded = false; + return wasLoaded; + } } + parse(buf, 0, true); if (len < 0) { debugs(4, DBG_CRITICAL, HERE << "failed to fully read: '" << path << "': " << xstrerror()); } file_close(fd); - /* Shrink memory size down to exact size. MemBuf has a tencendy - * to be rather large.. - */ - text = xstrdup(textbuf.buf); + wasLoaded = true; + return wasLoaded; +} + +bool strHdrAcptLangGetItem(const String &hdr, char *lang, int langLen, size_t &pos) +{ + while(pos < hdr.size()) { + char *dt = lang; + + if (!pos) { + /* skip any initial whitespace. */ + while (pos < hdr.size() && xisspace(hdr[pos])) pos++; + } + else { + // IFF we terminated the tag on whitespace or ';' we need to skip to the next ',' or end of header. + while (pos < hdr.size() && hdr[pos] != ',') pos++; + if (hdr[pos] == ',') pos++; + } + + /* + * Header value format: + * - sequence of whitespace delimited tags + * - each tag may suffix with ';'.* which we can ignore. + * - IFF a tag contains only two characters we can wildcard ANY translations matching: '-'? .* + * with preference given to an exact match. + */ + bool invalid_byte = false; + while (pos < hdr.size() && hdr[pos] != ';' && hdr[pos] != ',' && !xisspace(hdr[pos]) && dt < (lang + (langLen -1)) ) { + if (!invalid_byte) { +#if USE_HTTP_VIOLATIONS + // if accepting violations we may as well accept some broken browsers + // which may send us the right code, wrong ISO formatting. + if (hdr[pos] == '_') + *dt = '-'; + else +#endif + *dt = xtolower(hdr[pos]); + // valid codes only contain A-Z, hyphen (-) and * + if (*dt != '-' && *dt != '*' && (*dt < 'a' || *dt > 'z') ) + invalid_byte = true; + else + dt++; // move to next destination byte. + } + pos++; + } + *dt++ = '\0'; // nul-terminated the filename content string before system use. + + debugs(4, 9, HERE << "STATE: dt='" << dt << "', lang='" << lang << "', pos=" << pos << ", buf='" << ((pos < hdr.size()) ? hdr.substr(pos,hdr.size()) : "") << "'"); + + /* if we found anything we might use, try it. */ + if (*lang != '\0' && !invalid_byte) + return true; + } + return false; +} + +bool +TemplateFile::loadFor(HttpRequest *request) +{ + String hdr; + + if (loaded()) // already loaded? + return true; + + if (!request || !request->header.getList(HDR_ACCEPT_LANGUAGE, &hdr) ) + return false; - textbuf.clean(); + char lang[256]; + size_t pos = 0; // current parsing position in header string - return text; + debugs(4, 6, HERE << "Testing Header: '" << hdr << "'"); + + while ( strHdrAcptLangGetItem(hdr, lang, 256, pos) ) { + + /* wildcard uses the configured default language */ + if (lang[0] == '*' && lang[1] == '\0') { + debugs(4, 6, HERE << "Found language '" << lang << "'. Using configured default."); + return false; + } + + debugs(4, 6, HERE << "Found language '" << lang << "', testing for available template"); + + if (tryLoadTemplate(lang)) { + /* store the language we found for the Content-Language reply header */ + errLanguage = lang; + break; + } else if (Config.errorLogMissingLanguages) { + debugs(4, DBG_IMPORTANT, "WARNING: Error Pages Missing Language: " << lang); + } + } + + return loaded(); } /// \ingroup ErrorPageInternal static ErrorDynamicPageInfo * errorDynamicPageInfoCreate(int id, const char *page_name) { ErrorDynamicPageInfo *info = new ErrorDynamicPageInfo; info->id = id; info->page_name = xstrdup(page_name); info->page_redirect = static_cast(atoi(page_name)); /* WARNING on redirection status: * 2xx are permitted, but not documented officially. * - might be useful for serving static files (PAC etc) in special cases * 3xx require a URL suitable for Location: header. * - the current design does not allow for a Location: URI as well as a local file template * although this possibility is explicitly permitted in the specs. * 4xx-5xx require a local file template. * - sending Location: on these codes with no body is invalid by the specs. * - current result is Squid crashing or XSS problems as dynamic deny_info load random disk files. @@ -674,40 +798,41 @@ case 'b': mb.Printf("%d", getMyPort()); break; case 'B': if (building_deny_info_url) break; p = request ? ftpUrlWith2f(request) : "[no URL]"; break; case 'c': if (building_deny_info_url) break; p = errorPageName(type); break; case 'D': if (!allowRecursion) p = "%D"; // if recursion is not allowed, do not convert #if USE_SSL // currently only SSL error details implemented else if (detail) { + detail->useRequest(request); const String &errDetail = detail->toString(); if (errDetail.defined()) { MemBuf *detail_mb = ConvertText(errDetail.termedBuf(), false); mb.append(detail_mb->content(), detail_mb->contentSize()); delete detail_mb; do_quote = 0; } } #endif if (!mb.contentSize()) mb.Printf("[No Error Detail]"); break; case 'e': mb.Printf("%d", xerrno); break; case 'E': if (xerrno) mb.Printf("(%d) %s", xerrno, strerror(xerrno)); @@ -1064,159 +1189,77 @@ /* language is known unless error_directory override used */ if (!Config.errorDirectory) rep->header.putStr(HDR_CONTENT_LANGUAGE, "en"); } httpBodySet(&rep->body, content); /* do not memBufClean() or delete the content, it was absorbed by httpBody */ } return rep; } MemBuf * ErrorState::BuildContent() { const char *m = NULL; assert(page_id > ERR_NONE && page_id < error_page_count); #if USE_ERR_LOCALES - String hdr; - char dir[256]; - int l = 0; - const char *freePage = NULL; + ErrorPageFile *localeTmpl = NULL; /** error_directory option in squid.conf overrides translations. * Custom errors are always found either in error_directory or the templates directory. * Otherwise locate the Accept-Language header */ - if (!Config.errorDirectory && page_id < ERR_MAX && request && request->header.getList(HDR_ACCEPT_LANGUAGE, &hdr) ) { - - size_t pos = 0; // current parsing position in header string - char *reset = NULL; // where to reset the p pointer for each new tag file - char *dt = NULL; - - /* prep the directory path string to prevent snprintf ... */ - l = strlen(DEFAULT_SQUID_ERROR_DIR); - memcpy(dir, DEFAULT_SQUID_ERROR_DIR, l); - dir[ l++ ] = '/'; - reset = dt = dir + l; - - debugs(4, 6, HERE << "Testing Header: '" << hdr << "'"); - - while ( pos < hdr.size() ) { - - /* skip any initial whitespace. */ - while (pos < hdr.size() && xisspace(hdr[pos])) pos++; - - /* - * Header value format: - * - sequence of whitespace delimited tags - * - each tag may suffix with ';'.* which we can ignore. - * - IFF a tag contains only two characters we can wildcard ANY translations matching: '-'? .* - * with preference given to an exact match. - */ - bool invalid_byte = false; - while (pos < hdr.size() && hdr[pos] != ';' && hdr[pos] != ',' && !xisspace(hdr[pos]) && dt < (dir+256) ) { - if (!invalid_byte) { -#if USE_HTTP_VIOLATIONS - // if accepting violations we may as well accept some broken browsers - // which may send us the right code, wrong ISO formatting. - if (hdr[pos] == '_') - *dt = '-'; - else -#endif - *dt = xtolower(hdr[pos]); - // valid codes only contain A-Z, hyphen (-) and * - if (*dt != '-' && *dt != '*' && (*dt < 'a' || *dt > 'z') ) - invalid_byte = true; - else - dt++; // move to next destination byte. - } - pos++; - } - *dt++ = '\0'; // nul-terminated the filename content string before system use. - - debugs(4, 9, HERE << "STATE: dt='" << dt << "', reset='" << reset << "', pos=" << pos << ", buf='" << ((pos < hdr.size()) ? hdr.substr(pos,hdr.size()) : "") << "'"); - - /* if we found anything we might use, try it. */ - if (*reset != '\0' && !invalid_byte) { - - /* wildcard uses the configured default language */ - if (reset[0] == '*' && reset[1] == '\0') { - debugs(4, 6, HERE << "Found language '" << reset << "'. Using configured default."); - m = error_text[page_id]; - if (!Config.errorDirectory) - err_language = Config.errorDefaultLanguage; - break; - } - - debugs(4, 6, HERE << "Found language '" << reset << "', testing for available template in: '" << dir << "'"); - - m = errorTryLoadText( err_type_str[page_id], dir, false); - - if (m) { - /* store the language we found for the Content-Language reply header */ - err_language = xstrdup(reset); - freePage = m; - break; - } else if (Config.errorLogMissingLanguages) { - debugs(4, DBG_IMPORTANT, "WARNING: Error Pages Missing Language: " << reset); - } - -#if HAVE_GLOB - if ( (dt - reset) == 2) { - /* TODO glob the error directory for sub-dirs matching: '-*' */ - /* use first result. */ - debugs(4,2, HERE << "wildcard fallback errors not coded yet."); - } -#endif - } - - dt = reset; // reset for next tag testing. we replace the failed name instead of cloning. - - // IFF we terminated the tag on whitespace or ';' we need to skip to the next ',' or end of header. - while (pos < hdr.size() && hdr[pos] != ',') pos++; - if (hdr[pos] == ',') pos++; + if (!Config.errorDirectory && page_id < ERR_MAX) { + if (err_language && err_language != Config.errorDefaultLanguage) + safe_free(err_language); + + localeTmpl = new ErrorPageFile(err_type_str[page_id]); + if (localeTmpl->loadFor(request)) { + m = localeTmpl->text(); + assert(localeTmpl->language()); + err_language = xstrdup(localeTmpl->language()); } } #endif /* USE_ERR_LOCALES */ /** \par * If client-specific error templates are not enabled or available. * fall back to the old style squid.conf settings. */ if (!m) { m = error_text[page_id]; #if USE_ERR_LOCALES if (!Config.errorDirectory) err_language = Config.errorDefaultLanguage; #endif debugs(4, 2, HERE << "No existing error page language negotiated for " << errorPageName(page_id) << ". Using default error file."); } MemBuf *result = ConvertText(m, true); #if USE_ERR_LOCALES - safe_free(freePage); + if (localeTmpl) + delete localeTmpl; #endif - return result; } MemBuf *ErrorState::ConvertText(const char *text, bool allowRecursion) { MemBuf *content = new MemBuf; const char *p; const char *m = text; assert(m); content->init(); while ((p = strchr(m, '%'))) { content->append(m, p - m); /* copy */ const char *t = Convert(*++p, false, allowRecursion); /* convert */ content->Printf("%s", t); /* copy */ m = p + 1; /* advance */ } if (*m) content->Printf("%s", m); /* copy tail */ === modified file 'src/errorpage.h' --- src/errorpage.h 2011-05-13 21:04:03 +0000 +++ src/errorpage.h 2011-05-20 15:22:57 +0000 @@ -23,40 +23,41 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * * Copyright (c) 2003, Robert Collins */ #ifndef SQUID_ERRORPAGE_H #define SQUID_ERRORPAGE_H #include "squid.h" #if USE_AUTH #include "auth/UserRequest.h" #endif #include "cbdata.h" #include "ip/Address.h" +#include "MemBuf.h" #if USE_SSL #include "ssl/ErrorDetail.h" #endif /** \defgroup ErrorPageAPI Error Pages API \ingroup Components \section ErrorPageStringCodes Error Page % codes for text insertion. * \verbatim a - User identity x B - URL with FTP %2f hack x c - Squid error code x d - seconds elapsed since request received x D - Error details x e - errno x E - strerror() x f - FTP request line x F - FTP reply line x g - FTP server message x @@ -231,21 +232,88 @@ * \param entry ?? \param err This object is destroyed after use in this function. */ SQUIDCEXTERN void errorAppendEntry(StoreEntry *entry, ErrorState *err); /// \ingroup ErrorPageAPI SQUIDCEXTERN void errorStateFree(ErrorState * err); /// \ingroup ErrorPageAPI SQUIDCEXTERN err_type errorReservePageId(const char *page_name); /** \ingroup ErrorPageAPI * * This function creates a ErrorState object. */ SQUIDCEXTERN ErrorState *errorCon(err_type type, http_status, HttpRequest * request); SQUIDCEXTERN const char *errorPageName(int pageId); ///< error ID to string +/** + \ingroup ErrorPageAPI + * + * loads text templates used for error pages and details; + * supports translation of templates + */ +class TemplateFile { +public: + TemplateFile(const char *name); + virtual ~TemplateFile(){} + + /// return true if the data loaded from disk without any problem + bool loaded() const {return wasLoaded;} + + /** + * Load the page_name template from a file which probably exist at: + * (a) admin specified custom directory (error_directory) + * (b) default language translation directory (error_default_language) + * (c) English sub-directory where errors should ALWAYS exist + */ + bool loadDefault(); + + /** + * Load an error template for a given HTTP request. This function examines the + * Accept-Language header and select the first available template. If the default + * template selected (eg because of a "Accept-Language: *"), or not available + * template found this function return false. + */ + bool loadFor(HttpRequest *request); + + /** + * Load the file given by "path". It uses the "parse()" method. + * On success return true and sets the "defined" member + */ + bool loadFromFile(const char *path); + + /// The language used for the template + const char *language() {return errLanguage.termedBuf();} + + bool silent; ///< Whether to print error messages on cache.log file or not. It is user defined. + +protected: + /// Used to parse (if parsing required) the template data . + virtual bool parse(const char *buf, int len, bool eof) = 0; + + /** + * Try to load the "page_name" template for a given language "lang" + * from squid errors directory + \return true on success false otherwise + */ + bool tryLoadTemplate(const char *lang); + + bool wasLoaded; ///< True if the template data read from disk without any problem + String errLanguage; ///< The error language of the template. + String templateName; ///< The name of the template +}; + +/** + * Parses the Accept-Language header value and return one language item on + * each call. + * \param hdr is the Accept-Language header value + * \param lang a buffer given by the user to store parsed language + * \param langlen the length of the lang buffer + * \param pos it is used to store the state of parsing. Must be "0" on first call + * \return true on success, false otherwise + */ +bool strHdrAcptLangGetItem(const String &hdr, char *lang, int langLen, size_t &pos); #endif /* SQUID_ERRORPAGE_H */ === modified file 'src/ssl/ErrorDetail.cc' --- src/ssl/ErrorDetail.cc 2011-05-13 21:04:03 +0000 +++ src/ssl/ErrorDetail.cc 2011-05-20 21:16:51 +0000 @@ -1,242 +1,159 @@ #include "squid.h" +#include "errorpage.h" #include "ssl/ErrorDetail.h" #if HAVE_MAP #include #endif -struct SslErrorDetailEntry { +struct SslErrorEntry{ Ssl::ssl_error_t value; const char *name; - const char *detail; ///< for error page %D macro expansion; may contain macros - const char *descr; ///< short error description (for use in debug messages or error pages) }; static const char *SslErrorDetailDefaultStr = "SSL certificate validation error (%err_name): %ssl_subject"; //Use std::map to optimize search -typedef std::map SslErrorDetails; -SslErrorDetails TheSslDetail; +typedef std::map SslErrors; +SslErrors TheSslErrors; -static SslErrorDetailEntry TheSslDetailArray[] = { +static SslErrorEntry TheSslErrorArray[] = { {X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT, - "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT", - "SSL Certficate error: certificate issuer (CA) not known: %ssl_ca_name", - "Unable to get issuer certificate"}, + "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT"}, {X509_V_ERR_UNABLE_TO_GET_CRL, - "X509_V_ERR_UNABLE_TO_GET_CRL", - "%ssl_error_descr: %ssl_subject", - "Unable to get certificate CRL"}, + "X509_V_ERR_UNABLE_TO_GET_CRL"}, {X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE, - "X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE", - "%ssl_error_descr: %ssl_subject", - "Unable to decrypt certificate's signature"}, + "X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE"}, {X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE, - "X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE", - "%ssl_error_descr: %ssl_subject", - "Unable to decrypt CRL's signature"}, + "X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE"}, {X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY, - "X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY", - "Unable to decode issuer (CA) public key: %ssl_ca_name", - "Unable to decode issuer public key"}, + "X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY"}, {X509_V_ERR_CERT_SIGNATURE_FAILURE, - "X509_V_ERR_CERT_SIGNATURE_FAILURE", - "%ssl_error_descr: %ssl_subject", - "Certificate signature failure"}, + "X509_V_ERR_CERT_SIGNATURE_FAILURE"}, {X509_V_ERR_CRL_SIGNATURE_FAILURE, - "X509_V_ERR_CRL_SIGNATURE_FAILURE", - "%ssl_error_descr: %ssl_subject", - "CRL signature failure"}, + "X509_V_ERR_CRL_SIGNATURE_FAILURE"}, {X509_V_ERR_CERT_NOT_YET_VALID, - "X509_V_ERR_CERT_NOT_YET_VALID", - "SSL Certficate is not valid before: %ssl_notbefore", - "Certificate is not yet valid"}, + "X509_V_ERR_CERT_NOT_YET_VALID"}, {X509_V_ERR_CERT_HAS_EXPIRED, - "X509_V_ERR_CERT_HAS_EXPIRED", - "SSL Certificate expired on: %ssl_notafter", - "Certificate has expired"}, + "X509_V_ERR_CERT_HAS_EXPIRED"}, {X509_V_ERR_CRL_NOT_YET_VALID, - "X509_V_ERR_CRL_NOT_YET_VALID", - "%ssl_error_descr: %ssl_subject", - "CRL is not yet valid"}, + "X509_V_ERR_CRL_NOT_YET_VALID"}, {X509_V_ERR_CRL_HAS_EXPIRED, - "X509_V_ERR_CRL_HAS_EXPIRED", - "%ssl_error_descr: %ssl_subject", - "CRL has expired"}, + "X509_V_ERR_CRL_HAS_EXPIRED"}, {X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD, - "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD", - "SSL Certificate has invalid start date (the 'not before' field): %ssl_subject", - "Format error in certificate's notBefore field"}, + "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD"}, {X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD, - "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD", - "SSL Certificate has invalid expiration date (the 'not after' field): %ssl_subject", - "Format error in certificate's notAfter field"}, + "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD"}, {X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD, - "X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD", - "%ssl_error_descr: %ssl_subject", - "Format error in CRL's lastUpdate field"}, + "X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD"}, {X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD, - "X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD", - "%ssl_error_descr: %ssl_subject", - "Format error in CRL's nextUpdate field"}, + "X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD"}, {X509_V_ERR_OUT_OF_MEM, - "X509_V_ERR_OUT_OF_MEM", - "%ssl_error_descr", - "Out of memory"}, + "X509_V_ERR_OUT_OF_MEM"}, {X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT, - "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT", - "Self-signed SSL Certificate: %ssl_subject", - "Self signed certificate"}, + "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT"}, {X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN, - "X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN", - "Self-signed SSL Certificate in chain: %ssl_subject", - "Self signed certificate in certificate chain"}, + "X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN"}, {X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, - "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY", - "SSL Certficate error: certificate issuer (CA) not known: %ssl_ca_name", - "Unable to get local issuer certificate"}, + "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY"}, {X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE, - "X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE", - "%ssl_error_descr: %ssl_subject", - "Unable to verify the first certificate"}, + "X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE"}, {X509_V_ERR_CERT_CHAIN_TOO_LONG, - "X509_V_ERR_CERT_CHAIN_TOO_LONG", - "%ssl_error_descr: %ssl_subject", - "Certificate chain too long"}, + "X509_V_ERR_CERT_CHAIN_TOO_LONG"}, {X509_V_ERR_CERT_REVOKED, - "X509_V_ERR_CERT_REVOKED", - "%ssl_error_descr: %ssl_subject", - "Certificate revoked"}, + "X509_V_ERR_CERT_REVOKED"}, {X509_V_ERR_INVALID_CA, - "X509_V_ERR_INVALID_CA", - "%ssl_error_descr: %ssl_ca_name", - "Invalid CA certificate"}, + "X509_V_ERR_INVALID_CA"}, {X509_V_ERR_PATH_LENGTH_EXCEEDED, - "X509_V_ERR_PATH_LENGTH_EXCEEDED", - "%ssl_error_descr: %ssl_subject", - "Path length constraint exceeded"}, + "X509_V_ERR_PATH_LENGTH_EXCEEDED"}, {X509_V_ERR_INVALID_PURPOSE, - "X509_V_ERR_INVALID_PURPOSE", - "%ssl_error_descr: %ssl_subject", - "Unsupported certificate purpose"}, + "X509_V_ERR_INVALID_PURPOSE"}, {X509_V_ERR_CERT_UNTRUSTED, - "X509_V_ERR_CERT_UNTRUSTED", - "%ssl_error_descr: %ssl_subject", - "Certificate not trusted"}, + "X509_V_ERR_CERT_UNTRUSTED"}, {X509_V_ERR_CERT_REJECTED, - "X509_V_ERR_CERT_REJECTED", - "%ssl_error_descr: %ssl_subject", - "Certificate rejected"}, + "X509_V_ERR_CERT_REJECTED"}, {X509_V_ERR_SUBJECT_ISSUER_MISMATCH, - "X509_V_ERR_SUBJECT_ISSUER_MISMATCH", - "%ssl_error_descr: %ssl_ca_name", - "Subject issuer mismatch"}, + "X509_V_ERR_SUBJECT_ISSUER_MISMATCH"}, {X509_V_ERR_AKID_SKID_MISMATCH, - "X509_V_ERR_AKID_SKID_MISMATCH", - "%ssl_error_descr: %ssl_subject", - "Authority and subject key identifier mismatch"}, + "X509_V_ERR_AKID_SKID_MISMATCH"}, {X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH, - "X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH", - "%ssl_error_descr: %ssl_ca_name", - "Authority and issuer serial number mismatch"}, + "X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH"}, {X509_V_ERR_KEYUSAGE_NO_CERTSIGN, - "X509_V_ERR_KEYUSAGE_NO_CERTSIGN", - "%ssl_error_descr: %ssl_subject", - "Key usage does not include certificate signing"}, + "X509_V_ERR_KEYUSAGE_NO_CERTSIGN"}, {X509_V_ERR_APPLICATION_VERIFICATION, - "X509_V_ERR_APPLICATION_VERIFICATION", - "%ssl_error_descr: %ssl_subject", - "Application verification failure"}, - { SSL_ERROR_NONE, "SSL_ERROR_NONE", "No error", "No error" }, - {SSL_ERROR_NONE, NULL, NULL, NULL } + "X509_V_ERR_APPLICATION_VERIFICATION"}, + { SSL_ERROR_NONE, "SSL_ERROR_NONE"}, + {SSL_ERROR_NONE, NULL} }; -static void loadSslDetailMap() +static void loadSslErrorMap() { - assert(TheSslDetail.empty()); - for (int i = 0; TheSslDetailArray[i].name; ++i) { - TheSslDetail[TheSslDetailArray[i].value] = &TheSslDetailArray[i]; + assert(TheSslErrors.empty()); + for (int i = 0; TheSslErrorArray[i].name; ++i) { + TheSslErrors[TheSslErrorArray[i].value] = &TheSslErrorArray[i]; } } +Ssl::ssl_error_t Ssl::GetErrorCode(const char *name) +{ + for (int i = 0; TheSslErrorArray[i].name != NULL; i++) { + if (strcmp(name, TheSslErrorArray[i].name) == 0) + return TheSslErrorArray[i].value; + } + return SSL_ERROR_NONE; +} + Ssl::ssl_error_t Ssl::ParseErrorString(const char *name) { assert(name); - if (TheSslDetail.empty()) - loadSslDetailMap(); - - typedef SslErrorDetails::const_iterator SEDCI; - for (SEDCI i = TheSslDetail.begin(); i != TheSslDetail.end(); ++i) { - if (strcmp(name, i->second->name) == 0) - return i->second->value; - } + const Ssl::ssl_error_t ssl_error = GetErrorCode(name); + if (ssl_error != SSL_ERROR_NONE) + return ssl_error; if (xisdigit(*name)) { const long int value = strtol(name, NULL, 0); if (SQUID_SSL_ERROR_MIN <= value && value <= SQUID_SSL_ERROR_MAX) return value; fatalf("Too small or too bug SSL error code '%s'", name); } fatalf("Unknown SSL error name '%s'", name); return SSL_ERROR_SSL; // not reached } -static const SslErrorDetailEntry *getErrorRecord(Ssl::ssl_error_t value) +const char *Ssl::GetErrorName(Ssl::ssl_error_t value) { - if (TheSslDetail.empty()) - loadSslDetailMap(); + if (TheSslErrors.empty()) + loadSslErrorMap(); - const SslErrorDetails::const_iterator it = TheSslDetail.find(value); - if (it != TheSslDetail.end()) - return it->second; + const SslErrors::const_iterator it = TheSslErrors.find(value); + if (it != TheSslErrors.end()) + return it->second->name; return NULL; } const char * -Ssl::GetErrorName(Ssl::ssl_error_t value) -{ - if (const SslErrorDetailEntry *errorRecord = getErrorRecord(value)) - return errorRecord->name; - - return NULL; -} - -static const char *getErrorDetail(Ssl::ssl_error_t value) -{ - if (const SslErrorDetailEntry *errorRecord = getErrorRecord(value)) - return errorRecord->detail; - - // we must always return something because ErrorDetail::buildDetail - // will hit an assertion - return SslErrorDetailDefaultStr; -} - -const char * Ssl::GetErrorDescr(Ssl::ssl_error_t value) { - if (const SslErrorDetailEntry *errorRecord = getErrorRecord(value)) - return errorRecord->descr; - - return NULL; + return ErrorDetailsManager::GetInstance().getDefaultErrorDescr(value); } Ssl::ErrorDetail::err_frm_code Ssl::ErrorDetail::ErrorFormatingCodes[] = { {"ssl_subject", &Ssl::ErrorDetail::subject}, {"ssl_ca_name", &Ssl::ErrorDetail::ca_name}, {"ssl_cn", &Ssl::ErrorDetail::cn}, {"ssl_notbefore", &Ssl::ErrorDetail::notbefore}, {"ssl_notafter", &Ssl::ErrorDetail::notafter}, {"err_name", &Ssl::ErrorDetail::err_code}, {"ssl_error_descr", &Ssl::ErrorDetail::err_descr}, {NULL,NULL} }; /** * The subject of the current certification in text form */ const char *Ssl::ErrorDetail::subject() const { if (!peer_cert) return "[Not available]"; @@ -303,114 +220,135 @@ /** * The certificate "not after" field */ const char *Ssl::ErrorDetail::notafter() const { if (!peer_cert) return "[Not available]"; static char tmpBuffer[256]; // A temporary buffer ASN1_UTCTIME * tm = X509_get_notAfter(peer_cert.get()); Ssl::asn1timeToString(tm, tmpBuffer, sizeof(tmpBuffer)); return tmpBuffer; } /** * The string representation of the error_no */ const char *Ssl::ErrorDetail::err_code() const { static char tmpBuffer[64]; - const char *err = GetErrorName(error_no); + // We can use the GetErrorName but using the detailEntry is faster, + // so try it first. + const char *err = detailEntry.name.termedBuf(); + + // error details not loaded yet or not defined in error_details.txt, + // try the GetErrorName... + if (!err) + err = GetErrorName(error_no); + if (!err) { snprintf(tmpBuffer, 64, "%d", (int)error_no); err = tmpBuffer; } return err; } /** * A short description of the error_no */ const char *Ssl::ErrorDetail::err_descr() const { - if (const char *err = GetErrorDescr(error_no)) + if (error_no == SSL_ERROR_NONE) + return "[No Error]"; + if (const char *err = detailEntry.descr.termedBuf()) return err; return "[Not available]"; } /** * It converts the code to a string value. Currently the following * formating codes are supported: * %err_name: The name of the SSL error * %ssl_error_descr: A short description of the SSL error * %ssl_cn: The comma-separated list of common and alternate names * %ssl_subject: The certificate subject * %ssl_ca_name: The certificate issuer name * %ssl_notbefore: The certificate "not before" field * %ssl_notafter: The certificate "not after" field \retval the length of the code (the number of characters will be replaced by value) */ int Ssl::ErrorDetail::convert(const char *code, const char **value) const { *value = "-"; for (int i=0; ErrorFormatingCodes[i].code!=NULL; i++) { const int len = strlen(ErrorFormatingCodes[i].code); if (strncmp(code,ErrorFormatingCodes[i].code, len)==0) { ErrorDetail::fmt_action_t action = ErrorFormatingCodes[i].fmt_action; *value = (this->*action)(); return len; } } return 0; } /** * It uses the convert method to build the string errDetailStr using * a template message for the current SSL error. The template messages * can also contain normal error pages formating codes. * Currently the error template messages are hard-coded */ void Ssl::ErrorDetail::buildDetail() const { - char const *s = getErrorDetail(error_no); + char const *s = NULL; char const *p; char const *t; int code_len = 0; + if (ErrorDetailsManager::GetInstance().getErrorDetail(error_no, request.raw(), detailEntry)) + s = detailEntry.detail.termedBuf(); + + if (!s) + s = SslErrorDetailDefaultStr; + assert(s); while ((p = strchr(s, '%'))) { errDetailStr.append(s, p - s); code_len = convert(++p, &t); if (code_len) errDetailStr.append(t); else errDetailStr.append("%"); s = p + code_len; } errDetailStr.append(s, strlen(s)); } const String &Ssl::ErrorDetail::toString() const { if (!errDetailStr.defined()) buildDetail(); return errDetailStr; } /* We may do not want to use X509_dup but instead internal SSL locking: CRYPTO_add(&(cert->references),1,CRYPTO_LOCK_X509); peer_cert.reset(cert); */ Ssl::ErrorDetail::ErrorDetail( Ssl::ssl_error_t err_no, X509 *cert): error_no (err_no) { peer_cert.reset(X509_dup(cert)); + detailEntry.error_no = SSL_ERROR_NONE; } Ssl::ErrorDetail::ErrorDetail(Ssl::ErrorDetail const &anErrDetail) { error_no = anErrDetail.error_no; + request = anErrDetail.request; + if (anErrDetail.peer_cert.get()) { peer_cert.reset(X509_dup(anErrDetail.peer_cert.get())); } + + detailEntry = anErrDetail.detailEntry; } === modified file 'src/ssl/ErrorDetail.h' --- src/ssl/ErrorDetail.h 2011-05-13 21:04:03 +0000 +++ src/ssl/ErrorDetail.h 2011-05-20 10:38:47 +0000 @@ -1,85 +1,88 @@ #ifndef _SQUID_SSL_ERROR_DETAIL_H #define _SQUID_SSL_ERROR_DETAIL_H #include "err_detail_type.h" +#include "HttpRequest.h" +#include "ErrorDetailManager.h" #include "ssl/support.h" #include "ssl/gadgets.h" #if HAVE_OPENSSL_SSL_H #include #endif -// Custom SSL errors; assumes all official errors are positive -#define SQUID_X509_V_ERR_DOMAIN_MISMATCH -1 -// All SSL errors range: from smallest (negative) custom to largest SSL error -#define SQUID_SSL_ERROR_MIN SQUID_X509_V_ERR_DOMAIN_MISMATCH -#define SQUID_SSL_ERROR_MAX INT_MAX - namespace Ssl { -/// Squid defined error code (<0), an error code returned by SSL X509 api, or SSL_ERROR_NONE -typedef int ssl_error_t; - /** - \ingroup ServerProtocolSSLAPI + \ingroup ServerProtocolSSLAPI * The ssl_error_t representation of the error described by "name". + * This function also parses numeric arguments. */ ssl_error_t ParseErrorString(const char *name); /** \ingroup ServerProtocolSSLAPI + * The ssl_error_t code of the error described by "name". + */ +ssl_error_t GetErrorCode(const char *name); + +/** + \ingroup ServerProtocolSSLAPI * The string representation of the SSL error "value" */ const char *GetErrorName(ssl_error_t value); /** \ingroup ServerProtocolSSLAPI * A short description of the SSL error "value" */ const char *GetErrorDescr(ssl_error_t value); /** \ingroup ServerProtocolSSLAPI * Used to pass SSL error details to the error pages returned to the * end user. */ class ErrorDetail { public: ErrorDetail(ssl_error_t err_no, X509 *cert); ErrorDetail(ErrorDetail const &); const String &toString() const; ///< An error detail string to embed in squid error pages + void useRequest(HttpRequest *aRequest) { if (request != NULL) request = aRequest;} /// The error name to embed in squid error pages const char *errorName() const {return err_code();} private: typedef const char * (ErrorDetail::*fmt_action_t)() const; /** * Holds a formating code and its conversion method */ class err_frm_code { public: const char *code; ///< The formating code fmt_action_t fmt_action; ///< A pointer to the conversion method }; static err_frm_code ErrorFormatingCodes[]; ///< The supported formating codes const char *subject() const; const char *ca_name() const; const char *cn() const; const char *notbefore() const; const char *notafter() const; const char *err_code() const; const char *err_descr() const; int convert(const char *code, const char **value) const; void buildDetail() const; mutable String errDetailStr; ///< Caches the error detail message ssl_error_t error_no; ///< The error code X509_Pointer peer_cert; ///< A pointer to the peer certificate + mutable ErrorDetailEntry detailEntry; + HttpRequest::Pointer request; }; }//namespace Ssl #endif === added file 'src/ssl/ErrorDetailManager.cc' --- src/ssl/ErrorDetailManager.cc 1970-01-01 00:00:00 +0000 +++ src/ssl/ErrorDetailManager.cc 2011-05-20 15:43:58 +0000 @@ -0,0 +1,249 @@ +#include "squid.h" +#include "ErrorDetail.h" +#include "errorpage.h" +#include "ErrorDetailManager.h" + +void Ssl::errorDetailInitialize() +{ + Ssl::ErrorDetailsManager::GetInstance(); +} + +void Ssl::errorDetailClean() +{ + Ssl::ErrorDetailsManager::Shutdown(); +} + +namespace Ssl +{ + +/// manages error detail templates +class ErrorDetailFile : public TemplateFile{ +public: + explicit ErrorDetailFile(ErrorDetailsList::Pointer const details): TemplateFile("error-details.txt") { + buf.init(); theDetails = details; + } + +private: + MemBuf buf; + ErrorDetailsList::Pointer theDetails; + virtual bool parse(const char *buf, int len, bool eof); +}; +}// namespace Ssl + +/******************/ +bool +Ssl::ErrorDetailsList::getRecord(Ssl::ssl_error_t value, ErrorDetailEntry &entry) +{ + const ErrorDetails::const_iterator it = theList.find(value); + if (it != theList.end()) { + entry.error_no = it->second.error_no; + entry.name = it->second.name; + entry.detail = it->second.detail; + entry.descr = it->second.descr; + return true; + } + return false; +} + +const char * +Ssl::ErrorDetailsList::getErrorDescr(Ssl::ssl_error_t value) +{ + const ErrorDetails::const_iterator it = theList.find(value); + if (it != theList.end()) { + return it->second.descr.termedBuf(); + } + + return NULL; +} + +const char * +Ssl::ErrorDetailsList::getErrorDetail(Ssl::ssl_error_t value) +{ + const ErrorDetails::const_iterator it = theList.find(value); + if (it != theList.end()) { + return it->second.detail.termedBuf(); + } + + return NULL; +} + +Ssl::ErrorDetailsManager *Ssl::ErrorDetailsManager::TheDetailsManager = NULL; + +Ssl::ErrorDetailsManager &Ssl::ErrorDetailsManager::GetInstance() +{ + if (!TheDetailsManager) + TheDetailsManager = new Ssl::ErrorDetailsManager; + + assert(TheDetailsManager); + return *TheDetailsManager; +} + +void Ssl::ErrorDetailsManager::Shutdown() +{ + delete TheDetailsManager; + TheDetailsManager = NULL; +} + + +Ssl::ErrorDetailsManager::ErrorDetailsManager() +{ + theDefaultErrorDetails = new ErrorDetailsList(); + ErrorDetailFile detailTmpl(theDefaultErrorDetails); + detailTmpl.loadDefault(); +} + +Ssl::ErrorDetailsList::Pointer Ssl::ErrorDetailsManager::getCachedDetails(const char *lang) +{ + Cache::iterator it; + it = cache.find(lang); + if (it != cache.end()) { + debugs(83, 8, HERE << "Found template details in cache for language: " << lang); + return it->second; + } + + return NULL; +} + +void Ssl::ErrorDetailsManager::cacheDetails(ErrorDetailsList::Pointer &errorDetails) +{ + const char *lang = errorDetails->errLanguage.termedBuf(); + assert(lang); + if (cache.find(lang) == cache.end()) + cache[lang] = errorDetails; +} + +bool +Ssl::ErrorDetailsManager::getErrorDetail(Ssl::ssl_error_t value, HttpRequest *request, ErrorDetailEntry &entry) +{ +#if USE_ERR_LOCALES + String hdr; + if (request && request->header.getList(HDR_ACCEPT_LANGUAGE, &hdr)) { + ErrorDetailsList::Pointer errDetails = NULL; + //Try to retrieve from cache + size_t pos = 0; + char lang[256]; + // Get the first ellement of the Accept-Language header + strHdrAcptLangGetItem(hdr, lang, 256, pos); + errDetails = getCachedDetails(lang); // search in cache + + if (!errDetails) { // Else try to load from disk + debugs(83, 8, HERE << "Creating new ErrDetailList to read from disk"); + errDetails = new ErrorDetailsList(); + ErrorDetailFile detailTmpl(errDetails); + if(detailTmpl.loadFor(request)) { + if (detailTmpl.language()) { + debugs(83, 8, HERE << "Found details on disk for language " << detailTmpl.language()); + errDetails->errLanguage = detailTmpl.language(); + cacheDetails(errDetails); + } + } + } + + if (errDetails != NULL && errDetails->getRecord(value, entry)) + return true; + } +#endif + + // else try the default + if (theDefaultErrorDetails->getRecord(value, entry)) { + debugs(83, 8, HERE << "Found default details record for error: " << GetErrorName(value)); + return true; + } + + return false; +} + +const char * +Ssl::ErrorDetailsManager::getDefaultErrorDescr(Ssl::ssl_error_t value) +{ + return theDefaultErrorDetails->getErrorDescr(value); +} + +const char * +Ssl::ErrorDetailsManager::getDefaultErrorDetail(Ssl::ssl_error_t value) +{ + return theDefaultErrorDetails->getErrorDetail(value); +} + +// Use HttpHeaders parser to parse error-details.txt files +class DetailEntryParser: public HttpHeader { +public: + DetailEntryParser():HttpHeader(hoErrorDetail) {} +}; + +//The end of an error detrail entry is a double "\n". The headersEnd +// functions can detect it +inline size_t detailEntryEnd(const char *s, size_t len) {return headersEnd(s, len);} + +bool +Ssl::ErrorDetailFile::parse(const char *buffer, int len, bool eof) +{ + if (!theDetails) + return false; + + if (len) { + buf.append(buffer, len); + } + + if (eof) + buf.append("\n\n", 1); + + while (size_t size = detailEntryEnd(buf.content(), buf.contentSize())) { + const char *e = buf.content() + size; + + //ignore spaces, new lines and comment lines (starting with #) at the beggining + const char *s; + for (s = buf.content(); (*s == '\n' || *s == ' ' || *s == '\t' || *s == '#') && s < e; s++) { + if(*s == '#') + while(sgetErrorDetail(ssl_error)) { + debugs(83, DBG_IMPORTANT, HERE << + "WARNING! duplicate entry: " << errorName); + return false; + } + + ErrorDetailEntry &entry = theDetails->theList[ssl_error]; + entry.error_no = ssl_error; + entry.name = errorName; + String tmp = parser.getByName("detail"); + httpHeaderParseQuotedString(tmp.termedBuf(), tmp.size(), &entry.detail); + tmp = parser.getByName("descr"); + httpHeaderParseQuotedString(tmp.termedBuf(), tmp.size(), &entry.descr); + bool parseOK = entry.descr.defined() && entry.detail.defined(); + + if (!parseOK) { + debugs(83, DBG_IMPORTANT, HERE << + "WARNING! missing imporant field for detail error: " << errorName); + return false; + } + }// else {only spaces and black lines; just ignore} + + buf.consume(size); + } + debugs(83, 9, HERE << " Remain size: " << buf.contentSize() << " Content: " << buf.content()); + return true; +} === added file 'src/ssl/ErrorDetailManager.h' --- src/ssl/ErrorDetailManager.h 1970-01-01 00:00:00 +0000 +++ src/ssl/ErrorDetailManager.h 2011-05-20 15:44:14 +0000 @@ -0,0 +1,87 @@ +#ifndef _SQUID_SSL_ERRORDETAILMANAGER_H +#define _SQUID_SSL_ERRORDETAILMANAGER_H + +#include "ssl/support.h" +#include "ssl/gadgets.h" +#if HAVE_MAP +#include +#endif +#if HAVE_STRING +#include +#endif + +namespace Ssl +{ + +class ErrorDetailEntry { +public: + Ssl::ssl_error_t error_no; ///< The SSL error code + String name; ///< a name for the error + String detail; ///< for error page %D macro expansion; may contain macros + String descr; ///< short error description (for use in debug messages or error pages) +}; + +/** + * Used to hold an error-details.txt template in ram. An error-details,.txt is represented + * by a list of error detail entries (ErrorDetailEntry objects). + */ +class ErrorDetailsList : public RefCountable +{ +public: + typedef RefCount Pointer; + /** + * Retrieves the error details for a given error to "entry" object + * \return true on success, false otherwise + */ + bool getRecord(Ssl::ssl_error_t value, ErrorDetailEntry &entry); + const char *getErrorDescr(Ssl::ssl_error_t value); ///< an error description for an error if exist in list. + const char *getErrorDetail(Ssl::ssl_error_t value); ///< an error details for an error if exist in list. + + String errLanguage; ///< The language of the error-details.txt template, if any + typedef std::map ErrorDetails; + ErrorDetails theList; ///< The list of error details entries +}; + +/** + * It is used to load, manage and query multiple ErrorDetailLists + * objects. + */ +class ErrorDetailsManager { +public: + ErrorDetailsManager(); + + static ErrorDetailsManager &GetInstance(); ///< Instance class + static void Shutdown(); ///< reset the ErrorDetailsManager instance + + /** + * Retrieve error details for an error. This method examine the Accept-Language + * of the request to retrieve the error details for requested language else return + * the default error details. + * \param vale the error code + * \param request the current HTTP request. + * \param entry where to store error details + * \return true on success, false otherwise + */ + bool getErrorDetail(Ssl::ssl_error_t value, HttpRequest *request, ErrorDetailEntry &entry); + const char *getDefaultErrorDescr(Ssl::ssl_error_t value); ///< the default error description for a given error + const char *getDefaultErrorDetail(Ssl::ssl_error_t value); ///< the default error details for a given error + +private: + /// Return cached error details list for a given language if exist + ErrorDetailsList::Pointer getCachedDetails(const char *lang); + /// cache the given error details list. + void cacheDetails(ErrorDetailsList::Pointer &errorDetails); + + typedef std::map Cache; + Cache cache; ///< the error details list cache + ErrorDetailsList::Pointer theDefaultErrorDetails; ///< the default error details list + + /// An instance of ErrorDetailsManager to be used by squid (ssl/ErrorDetails.*) + static ErrorDetailsManager *TheDetailsManager; +}; + + +void errorDetailInitialize(); +void errorDetailClean(); +} //namespace Ssl +#endif === modified file 'src/ssl/Makefile.am' --- src/ssl/Makefile.am 2010-12-13 23:02:52 +0000 +++ src/ssl/Makefile.am 2011-05-17 14:44:59 +0000 @@ -6,37 +6,39 @@ EXTRA_PROGRAMS = \ ssl_crtd if USE_SSL_CRTD SSL_CRTD = ssl_crtd SSL_CRTD_SOURCE = \ helper.cc \ helper.h else SSL_CRTD = SSL_CRTD_SOURCE = endif libsslsquid_la_SOURCES = \ context_storage.cc \ context_storage.h \ Config.cc \ Config.h \ ErrorDetail.cc \ ErrorDetail.h \ + ErrorDetailManager.cc \ + ErrorDetailManager.h \ support.cc \ support.h libsslutil_la_SOURCES = \ gadgets.cc \ gadgets.h \ crtd_message.cc \ crtd_message.h \ $(SSL_CRTD_SOURCE) libexec_PROGRAMS = \ $(SSL_CRTD) if USE_SSL_CRTD ssl_crtd_SOURCES = ssl_crtd.cc certificate_db.cc certificate_db.h ssl_crtd_LDADD = $(SSLLIB) -lsslutil $(COMPAT_LIB) endif === modified file 'src/ssl/support.h' --- src/ssl/support.h 2011-05-13 07:59:19 +0000 +++ src/ssl/support.h 2011-05-20 10:33:22 +0000 @@ -38,40 +38,52 @@ #include "ssl/gadgets.h" #if HAVE_OPENSSL_SSL_H #include #endif #if HAVE_OPENSSL_X509V3_H #include #endif #if HAVE_OPENSSL_ERR_H #include #endif #if HAVE_OPENSSL_ENGINE_H #include #endif /** \defgroup ServerProtocolSSLAPI Server-Side SSL API \ingroup ServerProtocol */ +// Custom SSL errors; assumes all official errors are positive +#define SQUID_X509_V_ERR_DOMAIN_MISMATCH -1 +// All SSL errors range: from smallest (negative) custom to largest SSL error +#define SQUID_SSL_ERROR_MIN SQUID_X509_V_ERR_DOMAIN_MISMATCH +#define SQUID_SSL_ERROR_MAX INT_MAX + +namespace Ssl +{ +/// Squid defined error code (<0), an error code returned by SSL X509 api, or SSL_ERROR_NONE +typedef int ssl_error_t; +} //namespace Ssl + /// \ingroup ServerProtocolSSLAPI SSL_CTX *sslCreateServerContext(const char *certfile, const char *keyfile, int version, const char *cipher, const char *options, const char *flags, const char *clientCA, const char *CAfile, const char *CApath, const char *CRLfile, const char *dhpath, const char *context); /// \ingroup ServerProtocolSSLAPI SSL_CTX *sslCreateClientContext(const char *certfile, const char *keyfile, int version, const char *cipher, const char *options, const char *flags, const char *CAfile, const char *CApath, const char *CRLfile); /// \ingroup ServerProtocolSSLAPI int ssl_read_method(int, char *, int); /// \ingroup ServerProtocolSSLAPI int ssl_write_method(int, const char *, int); /// \ingroup ServerProtocolSSLAPI void ssl_shutdown_method(int); /// \ingroup ServerProtocolSSLAPI const char *sslGetUserEmail(SSL *ssl); /// \ingroup ServerProtocolSSLAPI