]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Configurable SSL error details messages
authorChristos Tsantilas <chtsanti@users.sourceforge.net>
Fri, 17 Jun 2011 07:46:48 +0000 (10:46 +0300)
committerChristos Tsantilas <chtsanti@users.sourceforge.net>
Fri, 17 Jun 2011 07:46:48 +0000 (10:46 +0300)
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.

This is a Measurement Factory project
----

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.

15 files changed:
configure.ac
errors/Makefile.am
errors/templates/error-details.txt [new file with mode: 0644]
scripts/mk-error-details-po.pl [new file with mode: 0644]
scripts/update-pot.sh
src/HttpHeader.cc
src/HttpHeader.h
src/errorpage.cc
src/errorpage.h
src/ssl/ErrorDetail.cc
src/ssl/ErrorDetail.h
src/ssl/ErrorDetailManager.cc [new file with mode: 0644]
src/ssl/ErrorDetailManager.h [new file with mode: 0644]
src/ssl/Makefile.am
src/ssl/support.h

index 21b0603fdc9afc7c512b0bfc7e70d0eadc4f0d3a..462aeb09cfd05865d6d5b414d2c0a097bde4b3d4 100644 (file)
@@ -3323,10 +3323,13 @@ SQUID_YESNO([$enableval],
 dnl Squid now has .po translation capability, given the right toolkit
 if test "x${enable_translation:=yes}" = "xyes" ; then
   AX_WITH_PROG([PO2HTML],[po2html])
+  AX_WITH_PROG([PO2TEXT],[po2txt])
 else
   PO2HTML="off"
+  PO2TEXT="off"
 fi
 AC_SUBST(PO2HTML)
+AC_SUBST(PO2TEXT)
 
 dnl Squid now has limited locale handling ...
 dnl on error pages
index 776ae2dd020c168a9f8b854b5e5e1cc704264316..a8fdc903036600f920289bcd315e3b685cf52e48 100644 (file)
@@ -17,6 +17,7 @@ include $(srcdir)/language.list
 CLEANFILES = $(TRANSLATE_LANGUAGES) translate-warn
 EXTRA_DIST = \
        $(ERROR_TEMPLATES) \
+       templates/error-details.txt \
        language.list \
        template.list \
        aliases alias-link.sh alias-upgrade errorpage.css TRANSLATORS COPYRIGHT
@@ -46,10 +47,11 @@ NOTIDY=`$(PO2HTML) --help | grep -o "\-\-notidy"`
            mkdir -p $(top_builddir)/errors/$$lang; \
            echo -n "Translate '$$lang' ..."; \
            for f in $(ERROR_TEMPLATES); do \
-                       page=`basename $$f`; \
-                       $(PO2HTML) $(NOTIDY) --progress=none -i $(top_srcdir)/errors/$$lang.po -t $(top_srcdir)/errors/$$f >$(top_builddir)/errors/$$lang/$$page || exit 1; \
-               done; \
-               echo "done."; \
+               page=`basename $$f`; \
+               $(PO2HTML) $(NOTIDY) --progress=none -i $(top_srcdir)/errors/$$lang.po -t $(top_srcdir)/errors/$$f >$(top_builddir)/errors/$$lang/$$page || exit 1; \
+           done; \
+           $(PO2TEXT) -t $(top_srcdir)/errors/templates/error-details.txt -i $(top_srcdir)/errors/$$lang.po > $(top_builddir)/errors/$$lang/error-details.txt || exit 1; \
+           echo "done."; \
        fi; \
        touch $@
 
@@ -70,7 +72,7 @@ install-data-local: translate
            if test -d $(srcdir)/$$l || test -d $(builddir)/$$l; then \
                $(mkinstalldirs) $(DESTDIR)$(DEFAULT_ERROR_DIR)/$$l; \
            fi; \
-           for f in $(ERROR_TEMPLATES); do \
+           for f in $(ERROR_TEMPLATES) templates/error-details.txt; do \
                page=`basename $$f`; \
                if test -f $(builddir)/$$l/$$page; then \
                    echo "$(INSTALL_DATA) $(builddir)/$$l/$$page $(DESTDIR)$(DEFAULT_ERROR_DIR)/$$l"; \
@@ -91,14 +93,14 @@ uninstall-local:
          l=`basename $$l .lang`; \
          echo "Located $$l for uninstall ..."; \
          if test -d $(srcdir)/$$l; then \
-               for f in $(srcdir)/$$l/ERR_*; do \
+               for f in $(srcdir)/$$l/ERR_* $(srcdir)/$$l/error-details.txt; do \
                        if test -f $(DESTDIR)$(DEFAULT_ERROR_DIR)/$$l/`basename $$f`; then \
                                $(RM) $(DESTDIR)$(DEFAULT_ERROR_DIR)/$$l/`basename $$f`; \
                        fi; \
                done; \
          fi ; \
          if test -d $(builddir)/$$l; then \
-               for f in $(builddir)/$$l/ERR_*; do \
+               for f in $(builddir)/$$l/ERR_* $(builddir)/$$l/error-details.txt; do \
                        if test -f $(DESTDIR)$(DEFAULT_ERROR_DIR)/$$l/`basename $$f`; then \
                                $(RM) $(DESTDIR)$(DEFAULT_ERROR_DIR)/$$l/`basename $$f`; \
                        fi; \
@@ -119,8 +121,8 @@ dist-hook: translate
          lang=`basename $$lang .lang`; \
          if test -d $$lang ; then \
                mkdir -p $(distdir)/$$lang; \
-               cp -p $(top_builddir)/errors/$$lang/ERR_*  $(distdir)/$$lang \
-                 || exit 1; \
+               cp -p $(top_builddir)/errors/$$lang/ERR_*  $(distdir)/$$lang || exit 1; \
+               cp -p $(top_builddir)/errors/$$lang/error-details.txt $(distdir)/$$lang || exit 1; \
          fi; \
        done
 
diff --git a/errors/templates/error-details.txt b/errors/templates/error-details.txt
new file mode 100644 (file)
index 0000000..af0152b
--- /dev/null
@@ -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"
diff --git a/scripts/mk-error-details-po.pl b/scripts/mk-error-details-po.pl
new file mode 100644 (file)
index 0000000..1767467
--- /dev/null
@@ -0,0 +1,137 @@
+#!/usr/bin/perl -w
+#
+# Author: Tsantilas Christos
+# (C) 2011 The Measurement Factory
+# 
+# Usage: 
+#     mk-error-details-po.pl error-details.txt
+#
+# This script read the error-details.txt error details template, and prints to the
+# std output the  contents of a possible .po file.
+# The error-details.txt file consist of records like the following:
+#
+#  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"
+#
+# The records separated with an empty line.
+# Comments starting with '#' supported.
+#
+
+use warnings;
+use strict;
+
+my $File;
+my $mode;
+
+$File = shift @ARGV or 
+    die "Usage: \n ".$0." error-detail-file\n\n";
+
+open(IN, "<$File") or
+    die "Can not open file '$File': $!";
+
+my @PO_RECORDS = ();
+my $lineNumber=0;
+while(my $line = <IN>) {
+    $lineNumber++;
+
+    if ($line =~ /^\#.*/ ) {
+        next;
+    }
+    elsif ($line =~ /^\s*$/ ) {
+        next;
+    }
+    my($rec) = "";
+    my($lineOffset) = 0;
+    do {
+        $rec = $rec.$line;
+        $line = <IN>;
+        $lineOffset++;
+    } while($line && $line !~ /^\s*$/);
+
+    processRecord(\@PO_RECORDS, $rec, $lineNumber);
+    $lineNumber= $lineNumber + $lineOffset;
+}
+
+foreach my $poRec (@PO_RECORDS) {
+    print $poRec->{"comment"};
+    print "msgid ".$poRec->{"msgid"}."\n";
+    print "msgstr ".$poRec->{"msgstr"}."\n\n";
+}
+
+exit(0);
+
+
+sub processRecord
+{
+    my($RECS, $rec, $lnumber) = @_;
+    my(@lines) = split /\n/, $rec;
+    my($currentField) = "";
+    my(%currentRec);
+    my $offset = 0;
+    foreach my $l (@lines) {
+        if ($l =~ /^name:(.*)/) {
+            $currentRec{"name"} = trim($1);
+            $currentField = "name";
+        }
+        elsif ( $l =~ /^detail:(.*)/ ) {
+            $currentRec{"detail"} = toCstr(trim_quoted($1));
+            $currentField = "detail";
+        }
+        elsif ($l =~ /^descr:(.*)/) {
+            $currentRec{"descr"} = toCstr(trim_quoted($1));
+            $currentField = "descr";
+        }
+        elsif($l = ~ /^(\s+.*)/  && defined($currentRec{$currentField})) {
+            my($fmtl) = toCstr($1);
+            $currentRec{$currentField}= $currentRec{$currentField}."\\n".$fmtl;
+        }
+    }
+
+    my (%poRecDetail, %poRecDescr);
+    
+    $poRecDetail{"comment"} = "#: $File+".$currentRec{"name"}.".detail:$lnumber\n";
+    $poRecDetail{"msgid"} = $currentRec{"detail"};
+    $poRecDetail{"msgstr"} = $currentRec{"detail"};
+    merge(\@$RECS, \%poRecDetail);
+
+    $poRecDescr{"comment"} = "#: $File+".$currentRec{"name"}.".descr:$lnumber\n";
+    $poRecDescr{"msgid"} = $currentRec{"descr"};
+    $poRecDescr{"msgstr"} = $currentRec{"descr"};
+    merge(\@$RECS, \%poRecDescr);
+}
+
+sub merge {
+    my ($RECS, $poRec) = @_;
+    foreach my $item (@$RECS) {
+        if ($poRec->{"msgid"} eq $item->{"msgid"}) {
+            $item->{"comment"} = $item->{"comment"}.$poRec->{"comment"};
+            return;
+        }
+    }
+    push @$RECS, $poRec;
+    return;
+}
+
+sub trim
+{
+    my $string = shift;
+    $string =~ s/^\s+//;
+    $string =~ s/\s+$//;
+    return $string;
+}
+
+sub trim_quoted
+{
+    my $string = shift;
+    $string =~ s/^\s+"/"/;
+    $string =~ s/"\s+$/"/;
+    return $string;
+}
+
+sub toCstr
+{
+    my $string = shift;
+    $string =~ s/\t/\\t/g;
+    return $string;
+}
index 9d9cdba772509bfa90d229d04b2357179637186e..158ba2aa7073bd6e68b96bb6037ce0d01a8347cb 100755 (executable)
@@ -17,11 +17,18 @@ rm errpages.pot
 # make a temp directory for all our workings...
 mkdir pot
 
-# Generate per-page disctionaries ...
+# Generate per-page dictionaries ...
 for f in `ls -1 ./templates/`; do
-       if test "${f}" != "generic" ; then
+       case ${f} in
+       error-details.txt)
+               ../scripts/mk-error-details-po.pl ./templates/${f} > ./pot/${f}.pot
+               ;;
+       ERR_*)
                html2po -i ./templates/${f} -P --duplicates=merge -o ./pot/${f}.pot
-       fi
+               ;;
+       *)
+               echo "SKIP: ${f}"
+       esac
 done
 
 # merge and sort the per-page .pot into a single master
index 30ef2389584a1acf172c4e99117b5c0007a78348..10bd05bb854e9fbc10c5e4d66f06d1c39575f827 100644 (file)
@@ -385,7 +385,7 @@ HttpHeader::HttpHeader() : owner (hoNone), len (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);
 }
@@ -420,7 +420,7 @@ 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);
@@ -436,24 +436,26 @@ HttpHeader::clean()
      * 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;
index a9cf983be19e379e3873674582db4a27d6a55e3f..84fa95b12e8cccf6c5e5afeac84255753e9e7638 100644 (file)
@@ -157,7 +157,11 @@ typedef enum {
     hoHtcpReply,
 #endif
     hoRequest,
-    hoReply
+    hoReply,
+#if USE_SSL
+    hoErrorDetail,
+#endif
+    hoEnd
 } http_hdr_owner_type;
 
 struct _HttpHeaderFieldAttrs {
index db7ecc342b9ace912877fc63e143675f41d48e40..1f380cedff536ed0805da3902fb6d0a548e35a10 100644 (file)
@@ -128,13 +128,30 @@ 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)
@@ -173,8 +190,8 @@ errorInitialize(void)
              *  (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.
@@ -188,7 +205,8 @@ errorInitialize(void)
 
             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;
             }
         }
     }
@@ -197,12 +215,12 @@ errorInitialize(void)
 
     // 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
@@ -221,6 +239,8 @@ errorClean(void)
         errorDynamicPageInfoDestroy(ErrorDynamicPages.pop_back());
 
     error_page_count = 0;
+
+    Ssl::errorDetailClean();
 }
 
 /// \ingroup ErrorPageInternal
@@ -236,63 +256,79 @@ errorFindHardText(err_type type)
     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)
 {
-    char *text = NULL;
+    assert(name);
+}
+
+bool
+TemplateFile::loadDefault()
+{
+    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: <tag> '-*'   */
+        /* 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);
 
@@ -300,14 +336,18 @@ errorTryLoadText(const char *page_name, const char *dir, bool silent)
         /* 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());
@@ -315,14 +355,98 @@ errorTryLoadText(const char *page_name, const char *dir, bool silent)
 
     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++;
+        }
 
-    textbuf.clean();
+        /*
+         * 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: <it> '-'? .*
+         *    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.
 
-    return text;
+        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;
+
+    char lang[256];
+    size_t pos = 0; // current parsing position in header string
+
+    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
@@ -686,6 +810,7 @@ ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion
 #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);
@@ -1076,103 +1201,21 @@ ErrorState::BuildContent()
     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: <it> '-'? .*
-             *    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: <tag> '-*'   */
-                    /* 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 */
@@ -1192,9 +1235,9 @@ ErrorState::BuildContent()
 
     MemBuf *result = ConvertText(m, true);
 #if USE_ERR_LOCALES
-    safe_free(freePage);
+    if (localeTmpl)
+        delete localeTmpl;
 #endif
-
     return result;
 }
 
index ddc3c6d353995f83bd16a257685d4d75eb235eec..a87662f477ce9c10146148e4ec5fb21c9984660d 100644 (file)
@@ -40,6 +40,7 @@
 #endif
 #include "cbdata.h"
 #include "ip/Address.h"
+#include "MemBuf.h"
 #if USE_SSL
 #include "ssl/ErrorDetail.h"
 #endif
@@ -248,4 +249,71 @@ SQUIDCEXTERN err_type errorReservePageId(const char *page_name);
 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 */
index 7c44cf774e356ae90acce64f231d674f43a779da..e3171205a1420c982282e6ab55c702b155a7f5ad 100644 (file)
 #include "squid.h"
+#include "errorpage.h"
 #include "ssl/ErrorDetail.h"
 #if HAVE_MAP
 #include <map>
 #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<Ssl::ssl_error_t, const SslErrorDetailEntry *> SslErrorDetails;
-SslErrorDetails TheSslDetail;
+typedef std::map<Ssl::ssl_error_t, const SslErrorEntry *> 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);
@@ -182,44 +121,22 @@ Ssl::ParseErrorString(const char *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;
-
-    return NULL;
-}
-
-const char *
-Ssl::GetErrorName(Ssl::ssl_error_t value)
-{
-    if (const SslErrorDetailEntry *errorRecord = getErrorRecord(value))
-        return errorRecord->name;
+    const SslErrors::const_iterator it = TheSslErrors.find(value);
+    if (it != TheSslErrors.end())
+        return it->second->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[] = {
@@ -320,7 +237,15 @@ const char *Ssl::ErrorDetail::notafter() const
 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;
@@ -333,7 +258,9 @@ const char *Ssl::ErrorDetail::err_code() const
  */
 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]";
 }
@@ -372,11 +299,17 @@ int Ssl::ErrorDetail::convert(const char *code, const char **value) const
  */
 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);
@@ -405,12 +338,17 @@ const String &Ssl::ErrorDetail::toString() const
 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;
 }
index 32486bb88ce4e94a54ac5b738c78d1dde9f807f5..131f4318afc8380a9c7c4f895e39d7af429276de 100644 (file)
@@ -2,6 +2,8 @@
 #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"
 
 #include <openssl/ssl.h>
 #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"
@@ -49,6 +49,7 @@ 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();}
 
@@ -79,6 +80,8 @@ private:
     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
diff --git a/src/ssl/ErrorDetailManager.cc b/src/ssl/ErrorDetailManager.cc
new file mode 100644 (file)
index 0000000..7cd8a37
--- /dev/null
@@ -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(s<e &&  *s != '\n') s++; // skip untill the end of line
+        }
+
+        if ( s != e) {
+            DetailEntryParser parser;
+            if (!parser.parse(s, e)) {
+                debugs(83, DBG_IMPORTANT, HERE <<
+                       "WARNING! parse error on:" << s);
+                return false;
+            }
+
+            const char *errorName = parser.getByName("name").termedBuf();
+            if (!errorName) {
+                debugs(83, DBG_IMPORTANT, HERE <<
+                       "WARNING! invalid or no error detail name on:" << s);
+                return false;
+            }
+
+            Ssl::ssl_error_t ssl_error = Ssl::GetErrorCode(errorName);
+            if (ssl_error == SSL_ERROR_NONE) {
+                debugs(83, DBG_IMPORTANT, HERE <<
+                       "WARNING! invalid error detail name: " << errorName);
+                return false;
+            }
+
+            if (theDetails->getErrorDetail(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;
+}
diff --git a/src/ssl/ErrorDetailManager.h b/src/ssl/ErrorDetailManager.h
new file mode 100644 (file)
index 0000000..2aeefc2
--- /dev/null
@@ -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 <map>
+#endif
+#if HAVE_STRING
+#include <string>
+#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<ErrorDetailsList> 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<Ssl::ssl_error_t, ErrorDetailEntry> 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<std::string, ErrorDetailsList::Pointer> 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
index 6eb6756a662206a6c36af504fe79512c457d7a5d..d8bad53191279c63a46a0ab4b0e436a8db046609 100644 (file)
@@ -23,6 +23,8 @@ libsslsquid_la_SOURCES = \
        Config.h \
        ErrorDetail.cc \
        ErrorDetail.h \
+       ErrorDetailManager.cc \
+       ErrorDetailManager.h \
        support.cc \
        support.h
 
index 4f03c7d7d94766f99861cf1bb14dd43c732237fc..eb24fd347dd676a4067d0135a29bcc61d5aa6a3a 100644 (file)
  \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);