Blame src/plugins/https-utils.c

Packit 8ea169
/*
Packit 8ea169
    Copyright (C) 2012  ABRT Team
Packit 8ea169
    Copyright (C) 2012  Red Hat, Inc.
Packit 8ea169
Packit 8ea169
    This program is free software; you can redistribute it and/or modify
Packit 8ea169
    it under the terms of the GNU General Public License as published by
Packit 8ea169
    the Free Software Foundation; either version 2 of the License, or
Packit 8ea169
    (at your option) any later version.
Packit 8ea169
Packit 8ea169
    This program is distributed in the hope that it will be useful,
Packit 8ea169
    but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 8ea169
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 8ea169
    GNU General Public License for more details.
Packit 8ea169
Packit 8ea169
    You should have received a copy of the GNU General Public License along
Packit 8ea169
    with this program; if not, write to the Free Software Foundation, Inc.,
Packit 8ea169
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Packit 8ea169
*/
Packit 8ea169
Packit 8ea169
#include "https-utils.h"
Packit 8ea169
Packit 8ea169
static bool ssl_allow_insecure = false;
Packit 8ea169
Packit 8ea169
/* Caller must free lang->locale if not NULL */
Packit 8ea169
void get_language(struct language *lang)
Packit 8ea169
{
Packit 8ea169
    lang->locale = NULL;
Packit 8ea169
    lang->encoding = NULL;
Packit 8ea169
    /*
Packit 8ea169
     * Note: ->accept_language and ->accept_charset will always be non-NULL:
Packit 8ea169
     * if we don't know them, they'll be ""; otherwise,
Packit 8ea169
     * they will be fully formed HTTP headers, with \r\n at the end.
Packit 8ea169
     * IOW: they are formatted for adding them to HTTP headers as-is.
Packit 8ea169
     */
Packit 8ea169
Packit 8ea169
    char *locale = setlocale(LC_ALL, NULL);
Packit 8ea169
    if (!locale)
Packit 8ea169
    {
Packit 8ea169
        lang->accept_language = xzalloc(1);
Packit 8ea169
        lang->accept_charset = xzalloc(1);
Packit 8ea169
        return;
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    lang->locale = xstrdup(locale);
Packit 8ea169
    lang->accept_language = xasprintf("Accept-Language: %s\r\n", locale);
Packit 8ea169
Packit 8ea169
    lang->encoding = strchr(lang->locale, '.');
Packit 8ea169
    if (!lang->encoding)
Packit 8ea169
    {
Packit 8ea169
        lang->accept_charset = xzalloc(1);
Packit 8ea169
        return;
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    *lang->encoding = '\0';
Packit 8ea169
    ++lang->encoding;
Packit 8ea169
    lang->accept_charset = xasprintf("Accept-Charset: %s\r\n", lang->encoding);
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
void alert_server_error(const char *peer_name)
Packit 8ea169
{
Packit 8ea169
    if (!peer_name)
Packit 8ea169
        alert(_("An error occurred on the server side."));
Packit 8ea169
    else
Packit 8ea169
    {
Packit 8ea169
        char *msg = xasprintf(_("A server-side error occurred on '%s'"), peer_name);
Packit 8ea169
        alert(msg);
Packit 8ea169
        free(msg);
Packit 8ea169
    }
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
void alert_connection_error(const char *peer_name)
Packit 8ea169
{
Packit 8ea169
    if (!peer_name)
Packit 8ea169
        alert(_("An error occurred while connecting to the server"));
Packit 8ea169
    else
Packit 8ea169
    {
Packit 8ea169
        char *msg = xasprintf(_("An error occurred while connecting to '%s'"), peer_name);
Packit 8ea169
        alert(msg);
Packit 8ea169
        free(msg);
Packit 8ea169
    }
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
static SECStatus ssl_bad_cert_handler(void *arg, PRFileDesc *sock)
Packit 8ea169
{
Packit 8ea169
    PRErrorCode err = PR_GetError();
Packit 8ea169
    CERTCertificate *cert = SSL_PeerCertificate(sock);
Packit 8ea169
    char *subject = CERT_NameToAscii(&cert->subject);
Packit 8ea169
    char *subject_cn = CERT_GetCommonName(&cert->subject);
Packit 8ea169
    char *issuer = CERT_NameToAscii(&cert->issuer);
Packit 8ea169
    CERT_DestroyCertificate(cert);
Packit 8ea169
    char *target_host = SSL_RevealURL(sock);
Packit 8ea169
    if (!target_host)
Packit 8ea169
        target_host = xstrdup("(unknown)");
Packit 8ea169
    switch (err)
Packit 8ea169
    {
Packit 8ea169
    case SEC_ERROR_CA_CERT_INVALID:
Packit 8ea169
        error_msg(_("Issuer certificate is invalid: '%s'."), issuer);
Packit 8ea169
        break;
Packit 8ea169
    case SEC_ERROR_UNTRUSTED_ISSUER:
Packit 8ea169
        error_msg(_("Certificate is signed by an untrusted issuer: '%s'."), issuer);
Packit 8ea169
        break;
Packit 8ea169
    case SSL_ERROR_BAD_CERT_DOMAIN:
Packit 8ea169
        error_msg(_("Certificate subject name '%s' does not match target host name '%s'."),
Packit 8ea169
                subject_cn, target_host);
Packit 8ea169
        break;
Packit 8ea169
    case SEC_ERROR_EXPIRED_CERTIFICATE:
Packit 8ea169
        error_msg(_("Remote certificate has expired."));
Packit 8ea169
        break;
Packit 8ea169
    case SEC_ERROR_UNKNOWN_ISSUER:
Packit 8ea169
        error_msg(_("Certificate issuer is not recognized: '%s'."), issuer);
Packit 8ea169
        break;
Packit 8ea169
    default:
Packit 8ea169
        error_msg(_("Bad certificate received. Subject '%s', issuer '%s'."),
Packit 8ea169
                subject, issuer);
Packit 8ea169
        break;
Packit 8ea169
    }
Packit 8ea169
    PR_Free(target_host);
Packit 8ea169
    return ssl_allow_insecure ? SECSuccess : SECFailure;
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
static SECStatus ssl_handshake_callback(PRFileDesc *sock, void *arg)
Packit 8ea169
{
Packit 8ea169
    return SECSuccess;
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
static const char *ssl_get_configdir()
Packit 8ea169
{
Packit 8ea169
    struct stat buf;
Packit 8ea169
    if (getenv("SSL_DIR"))
Packit 8ea169
    {
Packit 8ea169
        if (0 == stat(getenv("SSL_DIR"), &buf) &&
Packit 8ea169
            S_ISDIR(buf.st_mode))
Packit 8ea169
        {
Packit 8ea169
            return getenv("SSL_DIR");
Packit 8ea169
        }
Packit 8ea169
    }
Packit 8ea169
    if (0 == stat("/etc/pki/nssdb", &buf) &&
Packit 8ea169
        S_ISDIR(buf.st_mode))
Packit 8ea169
    {
Packit 8ea169
        return "/etc/pki/nssdb";
Packit 8ea169
    }
Packit 8ea169
    return NULL;
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
void ssl_connect(struct https_cfg *cfg, PRFileDesc **tcp_sock, PRFileDesc **ssl_sock)
Packit 8ea169
{
Packit 8ea169
    PRAddrInfo *addrinfo = PR_GetAddrInfoByName(cfg->url, PR_AF_UNSPEC, PR_AI_ADDRCONFIG);
Packit 8ea169
    if (!addrinfo)
Packit 8ea169
    {
Packit 8ea169
        alert_connection_error(cfg->url);
Packit 8ea169
        error_msg_and_die(_("Can't resolve host name '%s'. NSS error %d."), cfg->url, PR_GetError());
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    /* Hack */
Packit 8ea169
    ssl_allow_insecure = cfg->ssl_allow_insecure;
Packit 8ea169
Packit 8ea169
    void *enumptr = NULL;
Packit 8ea169
    PRNetAddr addr;
Packit 8ea169
    *tcp_sock = NULL;
Packit 8ea169
Packit 8ea169
    while ((enumptr = PR_EnumerateAddrInfo(enumptr, addrinfo, cfg->port, &addr)) != NULL)
Packit 8ea169
    {
Packit 8ea169
        if (addr.raw.family == PR_AF_INET || addr.raw.family == PR_AF_INET6)
Packit 8ea169
        {
Packit 8ea169
            *tcp_sock = PR_OpenTCPSocket(addr.raw.family);
Packit 8ea169
            break;
Packit 8ea169
        }
Packit 8ea169
    }
Packit 8ea169
    PR_FreeAddrInfo(addrinfo);
Packit 8ea169
    if (!*tcp_sock)
Packit 8ea169
        /* Host exists, but has neither IPv4 nor IPv6?? */
Packit 8ea169
        error_msg_and_die(_("Can't resolve host name '%s'."), cfg->url);
Packit 8ea169
Packit 8ea169
    /* These operations are expected to always succeed: */
Packit 8ea169
    PRSocketOptionData sock_option;
Packit 8ea169
    sock_option.option = PR_SockOpt_Nonblocking;
Packit 8ea169
    sock_option.value.non_blocking = PR_FALSE;
Packit 8ea169
    if (PR_SUCCESS != PR_SetSocketOption(*tcp_sock, &sock_option))
Packit 8ea169
        error_msg_and_die(_("Failed to set socket blocking mode."));
Packit 8ea169
    *ssl_sock = SSL_ImportFD(NULL, *tcp_sock);
Packit 8ea169
    if (!*ssl_sock)
Packit 8ea169
        error_msg_and_die(_("Failed to wrap TCP socket by SSL."));
Packit 8ea169
    if (SECSuccess != SSL_OptionSet(*ssl_sock, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE))
Packit 8ea169
        error_msg_and_die(_("Failed to enable client handshake to SSL socket."));
Packit 8ea169
    // https://bugzilla.redhat.com/show_bug.cgi?id=1189952
Packit 8ea169
    //if (SECSuccess != SSL_OptionSet(*ssl_sock, SSL_ENABLE_SSL2, PR_TRUE))
Packit 8ea169
    //    error_msg_and_die(_("Failed to enable SSL2."));
Packit 8ea169
    if (SECSuccess != SSL_OptionSet(*ssl_sock, SSL_ENABLE_SSL3, PR_TRUE))
Packit 8ea169
        error_msg_and_die(_("Failed to enable SSL3."));
Packit 8ea169
    if (SECSuccess != SSL_OptionSet(*ssl_sock, SSL_ENABLE_TLS, PR_TRUE))
Packit 8ea169
        error_msg_and_die(_("Failed to enable TLS."));
Packit 8ea169
    if (SECSuccess != SSL_SetURL(*ssl_sock, cfg->url))
Packit 8ea169
        error_msg_and_die(_("Failed to set URL to SSL socket."));
Packit 8ea169
Packit 8ea169
    /* This finally sends packets down the wire.
Packit 8ea169
     * If we fail here, then server denied our connect, or is down, etc.
Packit 8ea169
     * Need a good error message.
Packit 8ea169
     */
Packit 8ea169
    if (PR_SUCCESS != PR_Connect(*ssl_sock, &addr, PR_INTERVAL_NO_TIMEOUT))
Packit 8ea169
    {
Packit 8ea169
        alert_connection_error(cfg->url);
Packit 8ea169
        error_msg_and_die(_("Can't connect to '%s'"), cfg->url);
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    /* These should not fail either. (Why we don't set them earlier?) */
Packit 8ea169
    if (SECSuccess != SSL_BadCertHook(*ssl_sock,
Packit 8ea169
                                      (SSLBadCertHandler)ssl_bad_cert_handler,
Packit 8ea169
                                      NULL))
Packit 8ea169
    {
Packit 8ea169
        error_msg_and_die(_("Failed to set certificate hook."));
Packit 8ea169
    }
Packit 8ea169
    if (SECSuccess != SSL_HandshakeCallback(*ssl_sock,
Packit 8ea169
                                            (SSLHandshakeCallback)ssl_handshake_callback,
Packit 8ea169
                                            NULL))
Packit 8ea169
    {
Packit 8ea169
        error_msg_and_die(_("Failed to set handshake callback."));
Packit 8ea169
    }
Packit 8ea169
    if (SECSuccess != SSL_ResetHandshake(*ssl_sock, /*asServer:*/PR_FALSE))
Packit 8ea169
    {
Packit 8ea169
        error_msg_and_die(_("Failed to reset handshake."));
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    /* This performs SSL/TLS negotiation */
Packit 8ea169
    if (SECSuccess != SSL_ForceHandshake(*ssl_sock))
Packit 8ea169
    {
Packit 8ea169
        alert_connection_error(cfg->url);
Packit 8ea169
        error_msg_and_die(_("Failed to complete SSL handshake: NSS error %d."),
Packit 8ea169
                          PR_GetError());
Packit 8ea169
    }
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
void ssl_disconnect(PRFileDesc *ssl_sock)
Packit 8ea169
{
Packit 8ea169
    PRStatus pr_status = PR_Close(ssl_sock);
Packit 8ea169
    if (PR_SUCCESS != pr_status)
Packit 8ea169
        error_msg(_("Failed to close SSL socket."));
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
/**
Packit 8ea169
 * Parse a header's value from HTTP message. Only alnum values are supported.
Packit 8ea169
 * @returns
Packit 8ea169
 * Caller must free the returned value.
Packit 8ea169
 * If no header is found, NULL is returned.
Packit 8ea169
 */
Packit 8ea169
char *http_get_header_value(const char *message,
Packit 8ea169
                            const char *header_name)
Packit 8ea169
{
Packit 8ea169
    char *headers_end = strstr(message, "\r\n\r\n");
Packit 8ea169
    if (!headers_end)
Packit 8ea169
        return NULL;
Packit 8ea169
    char *search_string = xasprintf("\r\n%s:", header_name);
Packit 8ea169
    char *header = strcasestr(message, search_string);
Packit 8ea169
    if (!header || header > headers_end)
Packit 8ea169
    {
Packit 8ea169
        free(search_string);
Packit 8ea169
        return NULL;
Packit 8ea169
    }
Packit 8ea169
    header += strlen(search_string);
Packit 8ea169
    free(search_string);
Packit 8ea169
    while (*header == ' ')
Packit 8ea169
        ++header;
Packit 8ea169
    int len = 0;
Packit 8ea169
    while (header[len] && header[len] != '\r' && header[len] != '\n')
Packit 8ea169
        ++len;
Packit 8ea169
    while (header[len - 1] == ' ') /* strip spaces from right */
Packit 8ea169
        --len;
Packit 8ea169
    return xstrndup(header, len);
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
/**
Packit 8ea169
 * Parse body from HTTP message.
Packit 8ea169
 * Caller must free the returned value.
Packit 8ea169
 */
Packit 8ea169
char *http_get_body(const char *message)
Packit 8ea169
{
Packit 8ea169
    char *body = strstr(message, "\r\n\r\n");
Packit 8ea169
    if (!body)
Packit 8ea169
        return NULL;
Packit 8ea169
Packit 8ea169
    body += strlen("\r\n\r\n");
Packit 8ea169
    strtrimch(body, ' ');
Packit 8ea169
    return xstrdup(body);
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
int http_get_response_code(const char *message)
Packit 8ea169
{
Packit 8ea169
    if (0 != strncmp(message, "HTTP/", strlen("HTTP/")))
Packit 8ea169
        goto err;
Packit 8ea169
    char *space = strchr(message, ' ');
Packit 8ea169
    if (!space)
Packit 8ea169
        goto err;
Packit 8ea169
    int response_code;
Packit 8ea169
    if (1 != sscanf(space + 1, "%d", &response_code))
Packit 8ea169
        goto err;
Packit 8ea169
    return response_code;
Packit 8ea169
Packit 8ea169
 err:
Packit 8ea169
    alert_server_error(NULL);
Packit 8ea169
    /* Show bad header to the user */
Packit 8ea169
    char *sanitized = sanitize_utf8(message, (SANITIZE_ALL & ~SANITIZE_TAB));
Packit 8ea169
    error_msg_and_die(_("Malformed HTTP response header: '%s'"), sanitized ? sanitized : message);
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
void http_print_headers(FILE *file, const char *message)
Packit 8ea169
{
Packit 8ea169
    const char *headers_end = strstr(message, "\r\n\r\n");
Packit 8ea169
    const char *c;
Packit 8ea169
    if (!headers_end)
Packit 8ea169
        headers_end = message + strlen(message);
Packit 8ea169
    for (c = message; c != headers_end + 2; ++c)
Packit 8ea169
    {
Packit 8ea169
        if (*c == '\r')
Packit 8ea169
            continue;
Packit 8ea169
        putc(*c, file);
Packit 8ea169
    }
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
/**
Packit 8ea169
 * @returns
Packit 8ea169
 * Caller must free the returned value.
Packit 8ea169
 */
Packit 8ea169
char *tcp_read_response(PRFileDesc *tcp_sock)
Packit 8ea169
{
Packit 8ea169
    struct strbuf *strbuf = strbuf_new();
Packit 8ea169
    char buf[32768];
Packit 8ea169
    PRInt32 received = 0;
Packit 8ea169
    do {
Packit 8ea169
        received = PR_Recv(tcp_sock, buf, sizeof(buf) - 1, /*flags:*/0,
Packit 8ea169
                           PR_INTERVAL_NO_TIMEOUT);
Packit 8ea169
        if (received > 0)
Packit 8ea169
        {
Packit 8ea169
            buf[received] = '\0';
Packit 8ea169
            strbuf_append_str(strbuf, buf);
Packit 8ea169
        }
Packit 8ea169
        if (received == -1)
Packit 8ea169
        {
Packit 8ea169
            alert_connection_error(NULL);
Packit 8ea169
            error_msg_and_die(_("Receiving of data failed: NSS error %d."),
Packit 8ea169
                              PR_GetError());
Packit 8ea169
        }
Packit 8ea169
    } while (received > 0);
Packit 8ea169
    return strbuf_free_nobuf(strbuf);
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
/**
Packit 8ea169
 * Joins HTTP response body if the Transfer-Encoding is chunked.
Packit 8ea169
 * @param body raw HTTP response body (response without headers)
Packit 8ea169
 *             the function operates on the input, but returns it
Packit 8ea169
 *             to the initial state when done
Packit 8ea169
 * @returns Joined HTTP response body. Caller must free the value.
Packit 8ea169
*/
Packit 8ea169
char *http_join_chunked(char *body, int bodylen)
Packit 8ea169
{
Packit 8ea169
    struct strbuf *result = strbuf_new();
Packit 8ea169
    unsigned len;
Packit 8ea169
    int blen = bodylen > 0 ? bodylen : strlen(body);
Packit 8ea169
    char prevchar;
Packit 8ea169
    char *cursor = body;
Packit 8ea169
    while (cursor - body < blen)
Packit 8ea169
    {
Packit 8ea169
        if (sscanf(cursor, "%x", &len) != 1)
Packit 8ea169
            break;
Packit 8ea169
Packit 8ea169
        /* jump to next line */
Packit 8ea169
        cursor = strchr(cursor, '\n');
Packit 8ea169
        if (!cursor)
Packit 8ea169
            error_msg_and_die(_("Malformed chunked response."));
Packit 8ea169
        ++cursor;
Packit 8ea169
Packit 8ea169
        /* split chunk and append to result */
Packit 8ea169
        prevchar = cursor[len];
Packit 8ea169
        cursor[len] = '\0';
Packit 8ea169
        strbuf_append_str(result, cursor);
Packit 8ea169
        cursor[len] = prevchar;
Packit 8ea169
Packit 8ea169
        /* len + strlen("\r\n") */
Packit 8ea169
        cursor += len + 2;
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    return strbuf_free_nobuf(result);
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
void nss_init(SECMODModule **mod)
Packit 8ea169
{
Packit 8ea169
    SECStatus sec_status;
Packit 8ea169
    const char *configdir = ssl_get_configdir();
Packit 8ea169
    if (configdir)
Packit 8ea169
        sec_status = NSS_Initialize(configdir, "", "", "", NSS_INIT_READONLY);
Packit 8ea169
    else
Packit 8ea169
        sec_status = NSS_NoDB_Init(NULL);
Packit 8ea169
    if (SECSuccess != sec_status)
Packit 8ea169
        error_msg_and_die(_("Failed to initialize NSS."));
Packit 8ea169
Packit 8ea169
    // Initialize the trusted certificate store.
Packit 8ea169
    char module_name[] = "library=libnssckbi.so name=\"Root Certs\"";
Packit 8ea169
    *mod = SECMOD_LoadUserModule(module_name, NULL, PR_FALSE);
Packit 8ea169
    if (*mod == NULL || !(*mod)->loaded)
Packit 8ea169
    {
Packit 8ea169
        const PRErrorCode err = PR_GetError();
Packit 8ea169
        error_msg_and_die("error: NSPR error code %d: %s\n", err, PR_ErrorToName(err));
Packit 8ea169
    }
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
void nss_close(SECMODModule *mod)
Packit 8ea169
{
Packit 8ea169
    SSL_ClearSessionCache();
Packit 8ea169
    SECMOD_UnloadUserModule(mod);
Packit 8ea169
    SECMOD_DestroyModule(mod);
Packit 8ea169
    SECStatus sec_status = NSS_Shutdown();
Packit 8ea169
    if (SECSuccess != sec_status)
Packit 8ea169
        error_msg(_("Failed to shutdown NSS."));
Packit 8ea169
Packit 8ea169
    PR_Cleanup();
Packit 8ea169
}