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