/* ====================================================================
 * Copyright (c) 1998 Ralf S. Engelschall. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following
 *    disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by 
 *     Ralf S. Engelschall <rse@engelschall.com> for use in the
 *     mod_ssl project (http://www.engelschall.com/sw/mod_ssl/)."
 *
 * 4. The names "mod_ssl" must not be used to endorse or promote
 *    products derived from this software without prior written
 *    permission. For written permission, please contact
 *    rse@engelschall.com.
 *
 * 5. Products derived from this software may not be called "mod_ssl"
 *    nor may "mod_ssl" appear in their names without prior
 *    written permission of Ralf S. Engelschall.
 *
 * 6. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by 
 *     Ralf S. Engelschall <rse@engelschall.com> for use in the
 *     mod_ssl project (http://www.engelschall.com/sw/mod_ssl/)."
 *
 * THIS SOFTWARE IS PROVIDED BY RALF S. ENGELSCHALL ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL RALF S. ENGELSCHALL OR
 * HIS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 */


#include "mod_ssl.h"


/*  _________________________________________________________________
**
**  Logfile Support
**  _________________________________________________________________
*/

/*
 * Open the SSL logfile
 */
void ssl_log_open(server_rec *s, pool *p)
{
    char *szLogFile;
    SSLSrvConfigRec *pConfig = mySrvConfig(s);
    piped_log *pl;

    if (pConfig->szLogFile != NULL) {
        if (strEQ(pConfig->szLogFile, "/dev/null"))
            return;
        else if (pConfig->szLogFile[0] == '|') {
            szLogFile = ap_server_root_relative(p, pConfig->szLogFile+1);
            if ((pl = ap_open_piped_log(p, szLogFile)) == NULL) {
                ssl_log(s, SSL_LOG_ERROR|SSL_ADD_ERRNO,
                        "Cannot open reliable pipe to SSL logfile filter %s", szLogFile);
                ssl_die();
            }
            pConfig->fileLogFile = ap_pfdopen(p, ap_piped_log_write_fd(pl), "a");
            setbuf(pConfig->fileLogFile, NULL);
        }
        else {
            szLogFile = ap_server_root_relative(p, pConfig->szLogFile);
            if ((pConfig->fileLogFile = ap_pfopen(p, szLogFile, "a")) == NULL) {
                ssl_log(s, SSL_LOG_ERROR|SSL_ADD_ERRNO,
                        "Cannot open SSL logfile %s", szLogFile);
                ssl_die();
            }
            setbuf(pConfig->fileLogFile, NULL);
        }
    }
    return;
}

static struct {
    int   nLevel;
    char *szLevel;
} ssl_log_level2string[] = {
    { SSL_LOG_ERROR, "error" },
    { SSL_LOG_WARN,  "warn"  },
    { SSL_LOG_INFO,  "info"  },
    { SSL_LOG_DEBUG, "debug" },
    { 0, NULL }
};

static struct {
    char *cpPattern;
    char *cpAnnotation;
} ssl_log_annotate[] = {
    { "*envelope*bad*decrypt*", "wrong pass phrase!?" },
    { "*CLIENT_HELLO*unknown*protocol*", "speaking not SSL to HTTPS port!?" },
    { "*CLIENT_HELLO*http*request*", "speaking HTTP to HTTPS port!?" },
    { "*SSL3_READ_BYTES:sslv3*alert*bad*certificate*", "Subject DN in certificate not server name!?" },
    { NULL, NULL }
};

static char *ssl_log_annotation(char *error)
{
    char *errstr;
    int i;

    errstr = NULL;
    for (i = 0; ssl_log_annotate[i].cpPattern != NULL; i++) {
        if (ap_strcmp_match(error, ssl_log_annotate[i].cpPattern) == 0) {
            errstr = ssl_log_annotate[i].cpAnnotation;
            break;
        }
    }
    return errstr;
}

void ssl_log(server_rec *s, int level, const char *msg, ...)
{
    char tstr[80];
    char vstr[1024];
    char str[1024];
    int timz;
    struct tm *t;
    char sign;
    va_list ap;
    int add;
    char *levelstr;
    int i;
    char *astr;
    int safe_errno;
    unsigned long e;
    SSLSrvConfigRec *pConfig;
    char *cpE;
    char *cpA;
    
    /*  initialization  */
    va_start(ap, msg);
    safe_errno = errno;
    pConfig = mySrvConfig(s);

    /*  strip out additional flags  */
    add   = (level & ~SSL_LOG_MASK);
    level = (level & SSL_LOG_MASK);

    /*  we log only levels below  */
    if (pConfig->fileLogFile == NULL)
        return;
    if (   level > pConfig->nLogLevel
        && !(level & SSL_LOG_ERROR))
        return;

    /*  determine the time entry string  */
    t = ap_get_gmtoff(&timz);
    sign = (timz < 0 ? '-' : '+');
    if (timz < 0)
        timz = -timz;
    strftime(tstr, 80, "[%d/%b/%Y:%H:%M:%S ", t);
    ap_snprintf(tstr + strlen(tstr), 80-strlen(tstr), "%c%.2d%.2d]",
                sign, timz/60, timz%60);

    /*  determine level name  */
    levelstr = "unknown";
    for (i = 0; ssl_log_level2string[i].nLevel != 0; i++)
        if (ssl_log_level2string[i].nLevel == level)
            levelstr = ssl_log_level2string[i].szLevel;
    
    /*  create custom message  */
    ap_vsnprintf(vstr, sizeof(vstr), msg, ap);

    /*  write out SSLog message  */
    if ((add & SSL_ADD_ERRNO) && (add & SSL_ADD_SSLEAY))
        astr = " (System and SSLeay errors follow)";
    else if (add & SSL_ADD_ERRNO)
        astr = " (System error follows)";
    else if (add & SSL_ADD_SSLEAY)
        astr = " (SSLeay error follows)";
    else
        astr = "";
    if (level <= pConfig->nLogLevel) {
        ap_snprintf(str, sizeof(str), "%s [%s] %s%s\n", tstr, levelstr, vstr, astr);
        fprintf(pConfig->fileLogFile, str);
    }
    if (level & SSL_LOG_ERROR)
        ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, s, 
                     "mod_ssl: %s%s",  vstr, astr);

    /*  write out additional attachment messages  */
    if (add & SSL_ADD_ERRNO) {
        if (level <= pConfig->nLogLevel) {
            ap_snprintf(str, sizeof(str), "%s [%s] System: %s (errno: %d)\n", 
                        tstr, levelstr, strerror(safe_errno), safe_errno);
            fprintf(pConfig->fileLogFile, str);
        }
        if (level & SSL_LOG_ERROR)
            ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, s, 
                         "System: %s (errno: %d)", 
                         strerror(safe_errno), safe_errno);
    }
    if (add & SSL_ADD_SSLEAY) {
        while ((e = __SSLeay ERR_get_error())) {
            cpE = __SSLeay ERR_error_string(e, NULL);
            cpA = ssl_log_annotation(cpE);
            if (level <= pConfig->nLogLevel) {
                ap_snprintf(str, sizeof(str), "%s [%s] SSLeay: %s%s%s%s\n", 
                            tstr, levelstr, cpE,
                            cpA != NULL ? " [Hint: " : "", 
                            cpA != NULL ? cpA : "", cpA != NULL ? "]" : "");
                fprintf(pConfig->fileLogFile, str);
            }
            if (level & SSL_LOG_ERROR)
                ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, s, 
                             "SSLeay: %s%s%s%s", cpE,
                             cpA != NULL ? " [Hint: " : "", 
                             cpA != NULL ? cpA : "", cpA != NULL ? "]" : "");
        }
    }
    /* make sure the next log starts from a clean base */
    ERR_clear_error();

    /*  cleanup and return  */
    fflush(pConfig->fileLogFile);
    errno = safe_errno;
    va_end(ap);
    return;
}

void ssl_die(void)
{
    /*
     * This is used for fatal errors and here
     * it is common module practice to really 
     * exit from the complete program.
     */
    exit(1);
}

