From fe53177b2821497f0601687ad98987caae4d6687 Mon Sep 17 00:00:00 2001 From: Michal Toman Date: Thu, 9 Feb 2012 09:56:24 +0100 Subject: [PATCH 1/2] split HTTPS util functions to a separate file --- po/POTFILES.in | 1 + src/plugins/Makefile.am | 6 +- src/plugins/abrt-retrace-client.c | 432 +++---------------------------------- src/plugins/https-utils.c | 397 ++++++++++++++++++++++++++++++++++ src/plugins/https-utils.h | 64 ++++++ 5 files changed, 494 insertions(+), 406 deletions(-) create mode 100644 src/plugins/https-utils.c create mode 100644 src/plugins/https-utils.h diff --git a/po/POTFILES.in b/po/POTFILES.in index fdff6bb..d2a37de 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -24,6 +24,7 @@ src/plugins/abrt-retrace-client.c src/plugins/analyze_LocalGDB.xml.in src/plugins/analyze_RetraceServer.xml.in src/plugins/collect_xsession_errors.xml.in +src/plugins/https-utils.c src/cli/abrt-cli.c src/cli/list.c diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am index 8cbb354..2dc404c 100644 --- a/src/plugins/Makefile.am +++ b/src/plugins/Makefile.am @@ -64,7 +64,8 @@ EXTRA_DIST = \ analyze_LocalGDB.xml.in \ analyze_RetraceServer.xml.in \ analyze_VMcore.xml.in \ - abrt-action-analyze-vmcore + abrt-action-analyze-vmcore \ + https-utils.h abrt_dump_oops_SOURCES = \ abrt-dump-oops.c @@ -196,7 +197,8 @@ abrt_action_install_debuginfo_to_abrt_cache_LDADD = \ ../lib/libabrt.la abrt_retrace_client_SOURCES = \ - abrt-retrace-client.c + abrt-retrace-client.c \ + https-utils.c abrt_retrace_client_CFLAGS = \ -I$(srcdir)/../include \ -I$(srcdir)/../lib \ diff --git a/src/plugins/abrt-retrace-client.c b/src/plugins/abrt-retrace-client.c index 0760708..03883c5 100644 --- a/src/plugins/abrt-retrace-client.c +++ b/src/plugins/abrt-retrace-client.c @@ -16,19 +16,7 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if HAVE_LOCALE_H -# include -#endif -#include "libabrt.h" +#include "https-utils.h" #define MAX_FORMATS 16 #define MAX_RELEASES 32 @@ -50,15 +38,8 @@ struct retrace_settings char *supported_releases[MAX_RELEASES]; }; -struct language -{ - char *locale; - char *encoding; -}; - static const char *dump_dir_name = NULL; static const char *coredump = NULL; -static const char *url = "retrace.fedoraproject.org"; static const char *required_retrace[] = { FILENAME_COREDUMP, FILENAME_EXECUTABLE, FILENAME_PACKAGE, @@ -66,22 +47,16 @@ static const char *required_retrace[] = { FILENAME_COREDUMP, NULL }; static const char *required_vmcore[] = { FILENAME_VMCORE, NULL }; -static bool ssl_allow_insecure = false; -static bool http_show_headers = false; -static unsigned port = 443; static unsigned delay = 0; static int task_type = TASK_RETRACE; +static bool http_show_headers; -static void alert_server_error() +static struct https_cfg cfg = { - alert(_("An error occurred on the server side. Try again later.")); -} - -static void alert_connection_error() -{ - alert(_("An error occurred while connecting to the server. " - "Check your network connection and try again.")); -} + .url = "retrace.fedoraproject.org", + .port = 443, + .ssl_allow_insecure = false, +}; static void alert_crash_too_large() { @@ -89,26 +64,6 @@ static void alert_crash_too_large() "is too large. Try local retracing.")); } -/* Caller must free lang->locale if not NULL */ -static void get_language(struct language *lang) -{ - lang->locale = NULL; - lang->encoding = NULL; - - char *locale = setlocale(LC_ALL, NULL); - if (!locale) - return; - - lang->locale = xstrdup(locale); - lang->encoding = strchr(lang->locale, '.'); - - if (!lang->encoding) - return; - - *lang->encoding = '\0'; - ++lang->encoding; -} - /* Add an entry name to the args array if the entry name exists in a * problem directory. The entry is added to argindex offset to the array, * and the argindex is then increased. @@ -210,327 +165,19 @@ static int create_archive(bool unlink_temp) return tempfd; } -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; -} - -static PK11GenericObject *nss_load_cacert(const char *filename) -{ - PK11SlotInfo *slot = PK11_FindSlotByName("PEM Token #0"); - if (!slot) - error_msg_and_die(_("Failed to get slot 'PEM Token #0': %d."), PORT_GetError()); - - CK_ATTRIBUTE template[4]; - CK_OBJECT_CLASS class = CKO_CERTIFICATE; - -#define PK11_SETATTRS(x,id,v,l) (x)->type = (id); \ - (x)->pValue=(v); (x)->ulValueLen = (l) - - PK11_SETATTRS(&template[0], CKA_CLASS, &class, sizeof(class)); - CK_BBOOL cktrue = CK_TRUE; - PK11_SETATTRS(&template[1], CKA_TOKEN, &cktrue, sizeof(CK_BBOOL)); - PK11_SETATTRS(&template[2], CKA_LABEL, (unsigned char*)filename, strlen(filename)+1); - PK11_SETATTRS(&template[3], CKA_TRUST, &cktrue, sizeof(CK_BBOOL)); - PK11GenericObject *cert = PK11_CreateGenericObject(slot, template, 4, PR_FALSE); - PK11_FreeSlot(slot); - return cert; -} - -static char *ssl_get_password(PK11SlotInfo *slot, PRBool retry, void *arg) -{ - return NULL; -} - -static void ssl_connect(PRFileDesc **tcp_sock, - PRFileDesc **ssl_sock) -{ - PRAddrInfo *addrinfo = PR_GetAddrInfoByName(url, PR_AF_UNSPEC, PR_AI_ADDRCONFIG); - if (!addrinfo) - { - alert_connection_error(); - error_msg_and_die(_("Failed to get host by name: NSS error %d."), PR_GetError()); - } - - void *enumptr = NULL; - PRNetAddr addr; - *tcp_sock = NULL; - - while ((enumptr = PR_EnumerateAddrInfo(enumptr, addrinfo, port, &addr))) - { - 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) - error_msg_and_die(_("Failed to create a TCP socket")); - PRSocketOptionData sock_option; - sock_option.option = PR_SockOpt_Nonblocking; - sock_option.value.non_blocking = PR_FALSE; - PRStatus pr_status = PR_SetSocketOption(*tcp_sock, &sock_option); - if (PR_SUCCESS != pr_status) - { - PR_Close(*tcp_sock); - error_msg_and_die(_("Failed to set socket blocking mode.")); - } - *ssl_sock = SSL_ImportFD(NULL, *tcp_sock); - if (!*ssl_sock) - { - PR_Close(*tcp_sock); - error_msg_and_die(_("Failed to wrap TCP socket by SSL.")); - } - SECStatus sec_status = SSL_OptionSet(*ssl_sock, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE); - if (SECSuccess != sec_status) - { - PR_Close(*ssl_sock); - error_msg_and_die(_("Failed to enable client handshake to SSL socket.")); - } - if (SECSuccess != SSL_OptionSet(*ssl_sock, SSL_ENABLE_SSL2, PR_TRUE)) - error_msg_and_die(_("Failed to enable client handshake to SSL socket.")); - if (SECSuccess != SSL_OptionSet(*ssl_sock, SSL_ENABLE_SSL3, PR_TRUE)) - error_msg_and_die(_("Failed to enable client handshake to SSL socket.")); - if (SECSuccess != SSL_OptionSet(*ssl_sock, SSL_ENABLE_TLS, PR_TRUE)) - error_msg_and_die(_("Failed to enable client handshake to SSL socket.")); - - sec_status = SSL_SetURL(*ssl_sock, url); - if (SECSuccess != sec_status) - { - PR_Close(*ssl_sock); - error_msg_and_die(_("Failed to set URL to SSL socket.")); - } - pr_status = PR_Connect(*ssl_sock, &addr, PR_INTERVAL_NO_TIMEOUT); - if (PR_SUCCESS != pr_status) - { - PR_Close(*ssl_sock); - alert_connection_error(); - error_msg_and_die(_("Failed to connect SSL address.")); - } - if (SECSuccess != SSL_BadCertHook(*ssl_sock, - (SSLBadCertHandler)ssl_bad_cert_handler, - NULL)) - { - PR_Close(*ssl_sock); - error_msg_and_die(_("Failed to set certificate hook.")); - } - if (SECSuccess != SSL_HandshakeCallback(*ssl_sock, - (SSLHandshakeCallback)ssl_handshake_callback, - NULL)) - { - PR_Close(*ssl_sock); - error_msg_and_die(_("Failed to set handshake callback.")); - } - sec_status = SSL_ResetHandshake(*ssl_sock, /*asServer:*/PR_FALSE); - if (SECSuccess != sec_status) - { - PR_Close(*ssl_sock); - alert_server_error(); - error_msg_and_die(_("Failed to reset handshake.")); - } - sec_status = SSL_ForceHandshake(*ssl_sock); - if (SECSuccess != sec_status) - { - PR_Close(*ssl_sock); - alert_server_error(); - error_msg_and_die(_("Failed to force handshake: NSS error %d."), - PR_GetError()); - } -} - -static 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. - */ -static 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) - 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. - */ -static 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"); - - while (*body == ' ') - ++body; - - int len = strlen(body); - while (body[len - 1] == ' ') - --len; - - return xstrndup(body, len); -} - -static int http_get_response_code(const char *message) -{ - if (0 != strncmp(message, "HTTP/", strlen("HTTP/"))) - { - alert_server_error(); - error_msg_and_die(_("Invalid response from server: HTTP header not found.")); - } - char *space = strstr(message, " "); - if (!space) - { - alert_server_error(); - error_msg_and_die(_("Invalid response from server: HTTP header not found.")); - } - int response_code; - if (1 != sscanf(space + 1, "%d", &response_code)) - { - alert_server_error(); - error_msg_and_die(_("Invalid response from server: HTTP header not found.")); - } - return response_code; -} - -static 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. - */ -static 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(); - error_msg_and_die(_("Receiving of data failed: NSS error %d."), - PR_GetError()); - } - } while (received > 0); - return strbuf_free_nobuf(strbuf); -} - struct retrace_settings *get_settings() { struct retrace_settings *settings = xzalloc(sizeof(struct retrace_settings)); PRFileDesc *tcp_sock, *ssl_sock; - ssl_connect(&tcp_sock, &ssl_sock); + ssl_connect(&cfg, &tcp_sock, &ssl_sock); struct strbuf *http_request = strbuf_new(); strbuf_append_strf(http_request, "GET /settings HTTP/1.1\r\n" "Host: %s\r\n" "Content-Length: 0\r\n" "Connection: close\r\n" - "\r\n", url); + "\r\n", cfg.url); PRInt32 written = PR_Send(tcp_sock, http_request->buf, http_request->len, /*flags:*/0, PR_INTERVAL_NO_TIMEOUT); if (written == -1) @@ -847,7 +494,7 @@ static int create(bool delete_temp_archive, } PRFileDesc *tcp_sock, *ssl_sock; - ssl_connect(&tcp_sock, &ssl_sock); + ssl_connect(&cfg, &tcp_sock, &ssl_sock); /* Upload the archive. */ struct strbuf *http_request = strbuf_new(); strbuf_append_strf(http_request, @@ -857,7 +504,7 @@ static int create(bool delete_temp_archive, "Content-Length: %lld\r\n" "Connection: close\r\n" "X-Task-Type: %d\r\n", - url, (long long)file_stat.st_size, task_type); + cfg.url, (long long)file_stat.st_size, task_type); if (lang.encoding) strbuf_append_strf(http_request, @@ -1027,7 +674,7 @@ static void status(const char *task_id, get_language(&lang); PRFileDesc *tcp_sock, *ssl_sock; - ssl_connect(&tcp_sock, &ssl_sock); + ssl_connect(&cfg, &tcp_sock, &ssl_sock); struct strbuf *http_request = strbuf_new(); strbuf_append_strf(http_request, "GET /%s HTTP/1.1\r\n" @@ -1035,7 +682,7 @@ static void status(const char *task_id, "X-Task-Password: %s\r\n" "Content-Length: 0\r\n" "Connection: close\r\n", - task_id, url, task_password); + task_id, cfg.url, task_password); if (lang.encoding) strbuf_append_strf(http_request, @@ -1106,7 +753,7 @@ static void backtrace(const char *task_id, const char *task_password, get_language(&lang); PRFileDesc *tcp_sock, *ssl_sock; - ssl_connect(&tcp_sock, &ssl_sock); + ssl_connect(&cfg, &tcp_sock, &ssl_sock); struct strbuf *http_request = strbuf_new(); strbuf_append_strf(http_request, "GET /%s/backtrace HTTP/1.1\r\n" @@ -1114,7 +761,7 @@ static void backtrace(const char *task_id, const char *task_password, "X-Task-Password: %s\r\n" "Content-Length: 0\r\n" "Connection: close\r\n", - task_id, url, task_password); + task_id, cfg.url, task_password); if (lang.encoding) strbuf_append_strf(http_request, @@ -1175,7 +822,7 @@ static void run_log(const char *task_id, const char *task_password) get_language(&lang); PRFileDesc *tcp_sock, *ssl_sock; - ssl_connect(&tcp_sock, &ssl_sock); + ssl_connect(&cfg, &tcp_sock, &ssl_sock); struct strbuf *http_request = strbuf_new(); strbuf_append_strf(http_request, "GET /%s/log HTTP/1.1\r\n" @@ -1183,7 +830,7 @@ static void run_log(const char *task_id, const char *task_password) "X-Task-Password: %s\r\n" "Content-Length: 0\r\n" "Connection: close\r\n", - task_id, url, task_password); + task_id, cfg.url, task_password); if (lang.encoding) strbuf_append_strf(http_request, @@ -1317,9 +964,9 @@ int main(int argc, char **argv) OPT_BOOL('s', "syslog", NULL, _("log to syslog")), OPT_BOOL('k', "insecure", NULL, _("allow insecure connection to retrace server")), - OPT_STRING(0, "url", &url, "URL", + OPT_STRING(0, "url", &(cfg.url), "URL", _("retrace server URL")), - OPT_INTEGER(0, "port", &port, + OPT_INTEGER(0, "port", &(cfg.port), _("retrace server port")), OPT_BOOL(0, "headers", NULL, _("(debug) show received HTTP headers")), @@ -1346,11 +993,11 @@ int main(int argc, char **argv) char *env_url = getenv("RETRACE_SERVER_URL"); if (env_url) - url = env_url; + cfg.url = env_url; char *env_port = getenv("RETRACE_SERVER_PORT"); if (env_port) - port = xatou(env_port); + cfg.port = xatou(env_port); char *env_delay = getenv("ABRT_STATUS_DELAY"); if (env_delay) @@ -1358,7 +1005,7 @@ int main(int argc, char **argv) char *env_insecure = getenv("RETRACE_SERVER_INSECURE"); if (env_insecure) - ssl_allow_insecure = strncmp(env_insecure, "insecure", strlen("insecure")) == 0; + cfg.ssl_allow_insecure = strncmp(env_insecure, "insecure", strlen("insecure")) == 0; unsigned opts = parse_opts(argc, argv, options, usage); if (opts & OPT_syslog) @@ -1372,29 +1019,14 @@ int main(int argc, char **argv) else show_usage_and_die(usage, options); - if (!ssl_allow_insecure) - ssl_allow_insecure = opts & OPT_insecure; + if (!cfg.ssl_allow_insecure) + cfg.ssl_allow_insecure = opts & OPT_insecure; http_show_headers = opts & OPT_headers; /* Initialize NSS */ - 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.")); - - char *user_module = xstrdup("library=libnsspem.so name=PEM"); - SECMODModule* mod = SECMOD_LoadUserModule(user_module, NULL, PR_FALSE); - free(user_module); - if (!mod || !mod->loaded) - error_msg_and_die(_("Failed to initialize security module.")); - - PK11GenericObject *cert = nss_load_cacert("/etc/pki/tls/certs/ca-bundle.crt"); - PK11_SetPasswordFunc(ssl_get_password); - NSS_SetDomesticPolicy(); + SECMODModule *mod; + PK11GenericObject *cert; + nss_init(&mod, &cert); /* Run the desired operation. */ int result = 0; @@ -1438,15 +1070,7 @@ int main(int argc, char **argv) error_msg_and_die(_("Unknown operation: %s."), operation); /* Shutdown NSS. */ - SSL_ClearSessionCache(); - PK11_DestroyGenericObject(cert); - SECMOD_UnloadUserModule(mod); - SECMOD_DestroyModule(mod); - sec_status = NSS_Shutdown(); - if (SECSuccess != sec_status) - error_msg(_("Failed to shutdown NSS.")); - - PR_Cleanup(); + nss_close(mod, cert); return result; } diff --git a/src/plugins/https-utils.c b/src/plugins/https-utils.c new file mode 100644 index 0000000..0d583d4 --- /dev/null +++ b/src/plugins/https-utils.c @@ -0,0 +1,397 @@ +/* + 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; + + char *locale = setlocale(LC_ALL, NULL); + if (!locale) + return; + + lang->locale = xstrdup(locale); + lang->encoding = strchr(lang->locale, '.'); + + if (!lang->encoding) + return; + + *lang->encoding = '\0'; + ++lang->encoding; +} + +void alert_server_error() +{ + alert(_("An error occurred on the server side. Try again later.")); +} + +void alert_connection_error() +{ + alert(_("An error occurred while connecting to the server. " + "Check your network connection and try again.")); +} + +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; +} + +static PK11GenericObject *nss_load_cacert(const char *filename) +{ + PK11SlotInfo *slot = PK11_FindSlotByName("PEM Token #0"); + if (!slot) + error_msg_and_die(_("Failed to get slot 'PEM Token #0': %d."), PORT_GetError()); + + CK_ATTRIBUTE template[4]; + CK_OBJECT_CLASS class = CKO_CERTIFICATE; + +#define PK11_SETATTRS(x,id,v,l) \ + do { \ + (x)->type = (id); \ + (x)->pValue=(v); \ + (x)->ulValueLen = (l); \ + } while (0) + + PK11_SETATTRS(&template[0], CKA_CLASS, &class, sizeof(class)); + CK_BBOOL cktrue = CK_TRUE; + PK11_SETATTRS(&template[1], CKA_TOKEN, &cktrue, sizeof(CK_BBOOL)); + PK11_SETATTRS(&template[2], CKA_LABEL, (unsigned char*)filename, strlen(filename)+1); + PK11_SETATTRS(&template[3], CKA_TRUST, &cktrue, sizeof(CK_BBOOL)); + PK11GenericObject *cert = PK11_CreateGenericObject(slot, template, 4, PR_FALSE); + PK11_FreeSlot(slot); + return cert; +} + +static char *ssl_get_password(PK11SlotInfo *slot, PRBool retry, void *arg) +{ + 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(); + error_msg_and_die(_("Failed to get host by name: NSS error %d."), PR_GetError()); + } + + void *enumptr = NULL; + PRNetAddr addr; + *tcp_sock = NULL; + ssl_allow_insecure = cfg->ssl_allow_insecure; + + while ((enumptr = PR_EnumerateAddrInfo(enumptr, addrinfo, cfg->port, &addr))) + { + 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) + error_msg_and_die(_("Failed to create a TCP socket")); + PRSocketOptionData sock_option; + sock_option.option = PR_SockOpt_Nonblocking; + sock_option.value.non_blocking = PR_FALSE; + PRStatus pr_status = PR_SetSocketOption(*tcp_sock, &sock_option); + if (PR_SUCCESS != pr_status) + { + PR_Close(*tcp_sock); + error_msg_and_die(_("Failed to set socket blocking mode.")); + } + *ssl_sock = SSL_ImportFD(NULL, *tcp_sock); + if (!*ssl_sock) + { + PR_Close(*tcp_sock); + error_msg_and_die(_("Failed to wrap TCP socket by SSL.")); + } + SECStatus sec_status = SSL_OptionSet(*ssl_sock, SSL_HANDSHAKE_AS_CLIENT, PR_TRUE); + if (SECSuccess != sec_status) + { + PR_Close(*ssl_sock); + error_msg_and_die(_("Failed to enable client handshake to SSL socket.")); + } + + if (SECSuccess != SSL_OptionSet(*ssl_sock, SSL_ENABLE_SSL2, PR_TRUE)) + error_msg_and_die(_("Failed to enable client handshake to SSL socket.")); + if (SECSuccess != SSL_OptionSet(*ssl_sock, SSL_ENABLE_SSL3, PR_TRUE)) + error_msg_and_die(_("Failed to enable client handshake to SSL socket.")); + if (SECSuccess != SSL_OptionSet(*ssl_sock, SSL_ENABLE_TLS, PR_TRUE)) + error_msg_and_die(_("Failed to enable client handshake to SSL socket.")); + + sec_status = SSL_SetURL(*ssl_sock, cfg->url); + if (SECSuccess != sec_status) + { + PR_Close(*ssl_sock); + error_msg_and_die(_("Failed to set URL to SSL socket.")); + } + pr_status = PR_Connect(*ssl_sock, &addr, PR_INTERVAL_NO_TIMEOUT); + if (PR_SUCCESS != pr_status) + { + PR_Close(*ssl_sock); + alert_connection_error(); + error_msg_and_die(_("Failed to connect SSL address.")); + } + if (SECSuccess != SSL_BadCertHook(*ssl_sock, + (SSLBadCertHandler)ssl_bad_cert_handler, + NULL)) + { + PR_Close(*ssl_sock); + error_msg_and_die(_("Failed to set certificate hook.")); + } + if (SECSuccess != SSL_HandshakeCallback(*ssl_sock, + (SSLHandshakeCallback)ssl_handshake_callback, + NULL)) + { + PR_Close(*ssl_sock); + error_msg_and_die(_("Failed to set handshake callback.")); + } + sec_status = SSL_ResetHandshake(*ssl_sock, /*asServer:*/PR_FALSE); + if (SECSuccess != sec_status) + { + PR_Close(*ssl_sock); + alert_server_error(); + error_msg_and_die(_("Failed to reset handshake.")); + } + sec_status = SSL_ForceHandshake(*ssl_sock); + if (SECSuccess != sec_status) + { + PR_Close(*ssl_sock); + alert_server_error(); + error_msg_and_die(_("Failed to force 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/"))) + { + alert_server_error(); + error_msg_and_die(_("Invalid response from server: HTTP header not found.")); + } + char *space = strstr(message, " "); + if (!space) + { + alert_server_error(); + error_msg_and_die(_("Invalid response from server: HTTP header not found.")); + } + int response_code; + if (1 != sscanf(space + 1, "%d", &response_code)) + { + alert_server_error(); + error_msg_and_die(_("Invalid response from server: HTTP header not found.")); + } + return response_code; +} + +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(); + error_msg_and_die(_("Receiving of data failed: NSS error %d."), + PR_GetError()); + } + } while (received > 0); + return strbuf_free_nobuf(strbuf); +} + +void nss_init(SECMODModule **mod, PK11GenericObject **cert) +{ + 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.")); + + char *user_module = xstrdup("library=libnsspem.so name=PEM"); + *mod = SECMOD_LoadUserModule(user_module, NULL, PR_FALSE); + free(user_module); + if (!*mod || !(*mod)->loaded) + error_msg_and_die(_("Failed to initialize security module.")); + + *cert = nss_load_cacert("/etc/pki/tls/certs/ca-bundle.crt"); + PK11_SetPasswordFunc(ssl_get_password); + NSS_SetDomesticPolicy(); +} + +void nss_close(SECMODModule *mod, PK11GenericObject *cert) +{ + SSL_ClearSessionCache(); + PK11_DestroyGenericObject(cert); + SECMOD_UnloadUserModule(mod); + SECMOD_DestroyModule(mod); + SECStatus sec_status = NSS_Shutdown(); + if (SECSuccess != sec_status) + error_msg(_("Failed to shutdown NSS.")); + + PR_Cleanup(); +} diff --git a/src/plugins/https-utils.h b/src/plugins/https-utils.h new file mode 100644 index 0000000..84464f7 --- /dev/null +++ b/src/plugins/https-utils.h @@ -0,0 +1,64 @@ +/* + 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. +*/ + +#ifndef ABRT_HTTPS_UTILS_H_ +#define ABRT_HTTPS_UTILS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "libabrt.h" + +#if HAVE_LOCALE_H +#include +#endif + +struct language +{ + char *locale; + char *encoding; +}; + +struct https_cfg +{ + const char *url; + unsigned port; + bool ssl_allow_insecure; +}; + +void get_language(struct language *lang); +void alert_server_error(); +void alert_connection_error(); +void ssl_connect(struct https_cfg *cfg, PRFileDesc **tcp_sock, PRFileDesc **ssl_sock); +void ssl_disconnect(PRFileDesc *ssl_sock); +char *http_get_header_value(const char *message, const char *header_name); +char *http_get_body(const char *message); +int http_get_response_code(const char *message); +void http_print_headers(FILE *file, const char *message); +char *tcp_read_response(PRFileDesc *tcp_sock); +void nss_init(SECMODModule **mod, PK11GenericObject **cert); +void nss_close(SECMODModule *mod, PK11GenericObject *cert); + +#endif -- 1.7.1