From e67393a38e1739ff1b0453fdb3b68ff7e4fb36e9 Mon Sep 17 00:00:00 2001 From: Jakub Filak Date: Fri, 12 Sep 2014 17:20:01 +0200 Subject: [LIBREPORT PATCH 79/93] ureport: publish ureport.h and refactore uReport source Allow other plugins to submit uReport and allow external applications to use libreport to submit uReport. Related to rhbz1139987 Signed-off-by: Jakub Filak --- src/include/Makefile.am | 4 + src/include/ureport.h | 76 ++++ src/lib/Makefile.am | 4 +- src/lib/json.c | 180 ---------- src/lib/ureport.c | 180 ++++++++++ src/lib/ureport.h | 76 ---- src/plugins/Makefile.am | 4 +- src/plugins/reporter-ureport.c | 777 +++++++++++++++++++++++++++++++++++++++++ src/plugins/ureport.c | 777 ----------------------------------------- 9 files changed, 1043 insertions(+), 1035 deletions(-) create mode 100644 src/include/ureport.h delete mode 100644 src/lib/json.c create mode 100644 src/lib/ureport.c delete mode 100644 src/lib/ureport.h create mode 100644 src/plugins/reporter-ureport.c delete mode 100644 src/plugins/ureport.c diff --git a/src/include/Makefile.am b/src/include/Makefile.am index 806b93e..de44cda 100644 --- a/src/include/Makefile.am +++ b/src/include/Makefile.am @@ -15,3 +15,7 @@ libreport_include_HEADERS = \ internal_libreport.h \ internal_abrt_dbus.h \ xml_parser.h + +if BUILD_UREPORT +libreport_include_HEADERS += ureport.h +endif diff --git a/src/include/ureport.h b/src/include/ureport.h new file mode 100644 index 0000000..d341f6e --- /dev/null +++ b/src/include/ureport.h @@ -0,0 +1,76 @@ +/* + Copyright (C) 2012 ABRT team + Copyright (C) 2012 RedHat 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 UREPORT_H_ +#define UREPORT_H_ + +#include "problem_data.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * uReport generation configuration + */ +struct ureport_preferences +{ + GList *urp_auth_items; ///< list of file names included in 'auth' key +}; + +/* + * uReport server configuration + */ +struct ureport_server_config +{ + const char *ur_url; ///< Web service URL + bool ur_ssl_verify; ///< Verify HOST and PEER certificates + char *ur_client_cert; ///< Path to certificate used for client + ///< authentication (or NULL) + char *ur_client_key; ///< Private key for the certificate + map_string_t *ur_http_headers; ///< Additional HTTP headers + + struct ureport_preferences ur_prefs; ///< configuration for uReport generation +}; + +struct abrt_post_state; + +#define ureport_post libreport_ureport_post +struct post_state *ureport_post(const char *json_ureport, + struct ureport_server_config *config); + +#define ureport_attach_rhbz libreport_ureport_attach_rhbz +struct post_state *ureport_attach_rhbz(const char *bthash, int rhbz_bug_id, + struct ureport_server_config *config); + +#define ureport_attach_email libreport_ureport_attach_email +struct post_state *ureport_attach_email(const char *bthash, const char *email, + struct ureport_server_config *config); + +#define ureport_from_dump_dir libreport_ureport_from_dump_dir +char *ureport_from_dump_dir(const char *dump_dir_path); + +#define ureport_from_dump_dir_ext libreport_ureport_from_dump_dir_ext +char *ureport_from_dump_dir_ext(const char *dump_dir_path, + const struct ureport_preferences *preferences); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index d2ff675..7d9722a 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -72,6 +72,7 @@ libreport_la_CPPFLAGS = \ -DDEFAULT_DUMP_DIR_MODE=$(DEFAULT_DUMP_DIR_MODE) \ -DDUMP_DIR_OWNED_BY_USER=$(DUMP_DIR_OWNED_BY_USER) \ -DLARGE_DATA_TMP_DIR=\"$(LARGE_DATA_TMP_DIR)\" \ + $(JSON_C_CFLAGS) \ $(GLIB_CFLAGS) \ $(GOBJECT_CFLAGS) \ $(AUGEAS_CFLAGS) \ @@ -79,6 +80,7 @@ libreport_la_CPPFLAGS = \ libreport_la_LDFLAGS = \ -version-info 0:1:0 libreport_la_LIBADD = \ + $(JSON_C_LIBS) \ $(GLIB_LIBS) \ $(JOURNAL_LIBS) \ $(GOBJECT_LIBS) \ @@ -113,7 +115,7 @@ libreport_web_o += abrt_xmlrpc.h abrt_xmlrpc.c endif if BUILD_UREPORT -libreport_web_o += ureport.h json.c +libreport_web_o += ureport.c endif libreport_web_la_SOURCES = $(libreport_web_o) \ diff --git a/src/lib/json.c b/src/lib/json.c deleted file mode 100644 index 6fbbf39..0000000 --- a/src/lib/json.c +++ /dev/null @@ -1,180 +0,0 @@ -/* - Copyright (C) 2012 ABRT team - Copyright (C) 2012 RedHat 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 - -#include -#include - -#include "internal_libreport.h" -#include "ureport.h" -#include "libreport_curl.h" - - -static void ureport_add_str(struct json_object *ur, const char *key, - const char *s) -{ - struct json_object *jstring = json_object_new_string(s); - if (!jstring) - die_out_of_memory(); - - json_object_object_add(ur, key, jstring); -} - -char *ureport_from_dump_dir_ext(const char *dump_dir_path, const struct ureport_preferences *preferences) -{ - char *error_message; - struct sr_report *report = sr_abrt_report_from_dir(dump_dir_path, - &error_message); - - if (!report) - error_msg_and_die("%s", error_message); - - if (preferences != NULL && preferences->urp_auth_items != NULL) - { - struct dump_dir *dd = dd_opendir(dump_dir_path, DD_OPEN_READONLY); - if (!dd) - xfunc_die(); /* dd_opendir() already printed an error message */ - - GList *iter = preferences->urp_auth_items; - for ( ; iter != NULL; iter = g_list_next(iter)) - { - const char *key = (const char *)iter->data; - char *value = dd_load_text_ext(dd, key, - DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE | DD_FAIL_QUIETLY_ENOENT); - - if (value == NULL) - { - perror_msg("Cannot include '%s' in 'auth'", key); - continue; - } - - sr_report_add_auth(report, key, value); - } - - dd_close(dd); - } - - char *json_ureport = sr_report_to_json(report); - sr_report_free(report); - - return json_ureport; -} - -char *ureport_from_dump_dir(const char *dump_dir_path) -{ - return ureport_from_dump_dir_ext(dump_dir_path, /*no preferences*/NULL); -} - -char *new_json_attachment(const char *bthash, const char *type, const char *data) -{ - struct json_object *attachment = json_object_new_object(); - if (!attachment) - die_out_of_memory(); - - ureport_add_str(attachment, "bthash", bthash); - ureport_add_str(attachment, "type", type); - ureport_add_str(attachment, "data", data); - - char *result = xstrdup(json_object_to_json_string(attachment)); - json_object_put(attachment); - - return result; -} - -struct post_state *post_ureport(const char *json, struct ureport_server_config *config) -{ - int flags = POST_WANT_BODY | POST_WANT_ERROR_MSG; - - if (config->ur_ssl_verify) - flags |= POST_WANT_SSL_VERIFY; - - struct post_state *post_state = new_post_state(flags); - - if (config->ur_client_cert && config->ur_client_key) - { - post_state->client_cert_path = config->ur_client_cert; - post_state->client_key_path = config->ur_client_key; - } - - char **headers = xmalloc(sizeof(char *) * (3 + size_map_string(config->ur_http_headers))); - headers[0] = (char *)"Accept: application/json"; - headers[1] = (char *)"Connection: close"; - headers[2] = NULL; - - if (config->ur_http_headers != NULL) - { - unsigned i = 2; - const char *header; - const char *value; - map_string_iter_t iter; - init_map_string_iter(&iter, config->ur_http_headers); - while (next_map_string_iter(&iter, &header, &value)) - headers[i++] = xasprintf("%s: %s", header, value); - headers[i] = NULL; - } - - post_string_as_form_data(post_state, config->ur_url, "application/json", - (const char **)headers, json); - - /* Client authentication failed. Try again without client auth. - * CURLE_SSL_CONNECT_ERROR - cert not found/server doesnt trust the CA - * CURLE_SSL_CERTPROBLEM - malformed certificate/no permission - */ - if ((post_state->curl_result == CURLE_SSL_CONNECT_ERROR - || post_state->curl_result == CURLE_SSL_CERTPROBLEM) - && config->ur_client_cert && config->ur_client_key) - { - warn_msg("Authentication failed. Retrying unauthenticated."); - free_post_state(post_state); - post_state = new_post_state(flags); - - post_string_as_form_data(post_state, config->ur_url, "application/json", - (const char **)headers, json); - - } - - for (unsigned i = size_map_string(config->ur_http_headers); i != 0; --i) - free(headers[i + 1]); - free(headers); - - return post_state; -} - -struct post_state *ureport_attach_rhbz(const char *bthash, int rhbz_bug_id, - struct ureport_server_config *config) -{ - char *str_bug_id = xasprintf("%d", rhbz_bug_id); - char *json_attachment = new_json_attachment(bthash, "RHBZ", str_bug_id); - struct post_state *post_state = post_ureport(json_attachment, config); - free(str_bug_id); - free(json_attachment); - - return post_state; -} - -struct post_state *ureport_attach_email(const char *bthash, const char *email, - struct ureport_server_config *config) -{ - char *json_attachment = new_json_attachment(bthash, "email", email); - struct post_state *post_state = post_ureport(json_attachment, config); - free(json_attachment); - - return post_state; -} diff --git a/src/lib/ureport.c b/src/lib/ureport.c new file mode 100644 index 0000000..761fe62 --- /dev/null +++ b/src/lib/ureport.c @@ -0,0 +1,180 @@ +/* + Copyright (C) 2012,2014 ABRT team + Copyright (C) 2012,2014 RedHat 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 + +#include +#include + +#include "internal_libreport.h" +#include "ureport.h" +#include "libreport_curl.h" + + +static void ureport_add_str(struct json_object *ur, const char *key, + const char *s) +{ + struct json_object *jstring = json_object_new_string(s); + if (!jstring) + die_out_of_memory(); + + json_object_object_add(ur, key, jstring); +} + +char *ureport_from_dump_dir_ext(const char *dump_dir_path, const struct ureport_preferences *preferences) +{ + char *error_message; + struct sr_report *report = sr_abrt_report_from_dir(dump_dir_path, + &error_message); + + if (!report) + error_msg_and_die("%s", error_message); + + if (preferences != NULL && preferences->urp_auth_items != NULL) + { + struct dump_dir *dd = dd_opendir(dump_dir_path, DD_OPEN_READONLY); + if (!dd) + xfunc_die(); /* dd_opendir() already printed an error message */ + + GList *iter = preferences->urp_auth_items; + for ( ; iter != NULL; iter = g_list_next(iter)) + { + const char *key = (const char *)iter->data; + char *value = dd_load_text_ext(dd, key, + DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE | DD_FAIL_QUIETLY_ENOENT); + + if (value == NULL) + { + perror_msg("Cannot include '%s' in 'auth'", key); + continue; + } + + sr_report_add_auth(report, key, value); + } + + dd_close(dd); + } + + char *json_ureport = sr_report_to_json(report); + sr_report_free(report); + + return json_ureport; +} + +char *ureport_from_dump_dir(const char *dump_dir_path) +{ + return ureport_from_dump_dir_ext(dump_dir_path, /*no preferences*/NULL); +} + +static char *new_json_attachment(const char *bthash, const char *type, const char *data) +{ + struct json_object *attachment = json_object_new_object(); + if (!attachment) + die_out_of_memory(); + + ureport_add_str(attachment, "bthash", bthash); + ureport_add_str(attachment, "type", type); + ureport_add_str(attachment, "data", data); + + char *result = xstrdup(json_object_to_json_string(attachment)); + json_object_put(attachment); + + return result; +} + +struct post_state *ureport_post(const char *json, struct ureport_server_config *config) +{ + int flags = POST_WANT_BODY | POST_WANT_ERROR_MSG; + + if (config->ur_ssl_verify) + flags |= POST_WANT_SSL_VERIFY; + + struct post_state *post_state = new_post_state(flags); + + if (config->ur_client_cert && config->ur_client_key) + { + post_state->client_cert_path = config->ur_client_cert; + post_state->client_key_path = config->ur_client_key; + } + + char **headers = xmalloc(sizeof(char *) * (3 + size_map_string(config->ur_http_headers))); + headers[0] = (char *)"Accept: application/json"; + headers[1] = (char *)"Connection: close"; + headers[2] = NULL; + + if (config->ur_http_headers != NULL) + { + unsigned i = 2; + const char *header; + const char *value; + map_string_iter_t iter; + init_map_string_iter(&iter, config->ur_http_headers); + while (next_map_string_iter(&iter, &header, &value)) + headers[i++] = xasprintf("%s: %s", header, value); + headers[i] = NULL; + } + + post_string_as_form_data(post_state, config->ur_url, "application/json", + (const char **)headers, json); + + /* Client authentication failed. Try again without client auth. + * CURLE_SSL_CONNECT_ERROR - cert not found/server doesnt trust the CA + * CURLE_SSL_CERTPROBLEM - malformed certificate/no permission + */ + if ((post_state->curl_result == CURLE_SSL_CONNECT_ERROR + || post_state->curl_result == CURLE_SSL_CERTPROBLEM) + && config->ur_client_cert && config->ur_client_key) + { + warn_msg("Authentication failed. Retrying unauthenticated."); + free_post_state(post_state); + post_state = new_post_state(flags); + + post_string_as_form_data(post_state, config->ur_url, "application/json", + (const char **)headers, json); + + } + + for (unsigned i = size_map_string(config->ur_http_headers); i != 0; --i) + free(headers[i + 1]); + free(headers); + + return post_state; +} + +struct post_state *ureport_attach_rhbz(const char *bthash, int rhbz_bug_id, + struct ureport_server_config *config) +{ + char *str_bug_id = xasprintf("%d", rhbz_bug_id); + char *json_attachment = new_json_attachment(bthash, "RHBZ", str_bug_id); + struct post_state *post_state = ureport_post(json_attachment, config); + free(str_bug_id); + free(json_attachment); + + return post_state; +} + +struct post_state *ureport_attach_email(const char *bthash, const char *email, + struct ureport_server_config *config) +{ + char *json_attachment = new_json_attachment(bthash, "email", email); + struct post_state *post_state = ureport_post(json_attachment, config); + free(json_attachment); + + return post_state; +} diff --git a/src/lib/ureport.h b/src/lib/ureport.h deleted file mode 100644 index 319aca9..0000000 --- a/src/lib/ureport.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - Copyright (C) 2012 ABRT team - Copyright (C) 2012 RedHat 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 UREPORT_H_ -#define UREPORT_H_ - -#include "problem_data.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * uReport generation configuration - */ -struct ureport_preferences -{ - GList *urp_auth_items; ///< list of file names included in 'auth' key -}; - -/* - * uReport server configuration - */ -struct ureport_server_config -{ - const char *ur_url; ///< Web service URL - bool ur_ssl_verify; ///< Verify HOST and PEER certificates - char *ur_client_cert; ///< Path to certificate used for client - ///< authentication (or NULL) - char *ur_client_key; ///< Private key for the certificate - map_string_t *ur_http_headers; ///< Additional HTTP headers - - struct ureport_preferences ur_prefs; ///< configuration for uReport generation -}; - -struct abrt_post_state; - -#define post_ureport libreport_post_ureport -struct post_state *post_ureport(const char *json_ureport, - struct ureport_server_config *config); - -#define ureport_attach_rhbz libreport_ureport_attach_rhbz -struct post_state *ureport_attach_rhbz(const char *bthash, int rhbz_bug_id, - struct ureport_server_config *config); - -#define ureport_attach_email libreport_ureport_attach_email -struct post_state *ureport_attach_email(const char *bthash, const char *email, - struct ureport_server_config *config); - -#define ureport_from_dump_dir libreport_ureport_from_dump_dir -char *ureport_from_dump_dir(const char *dump_dir_path); - -#define ureport_from_dump_dir_ext libreport_ureport_from_dump_dir_ext -char *ureport_from_dump_dir_ext(const char *dump_dir_path, - const struct ureport_preferences *preferences); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am index 7ec5219..7ec08d7 100644 --- a/src/plugins/Makefile.am +++ b/src/plugins/Makefile.am @@ -250,15 +250,17 @@ endif if BUILD_UREPORT reporter_ureport_SOURCES = \ - ureport.c + reporter-ureport.c reporter_ureport_CPPFLAGS = \ -I$(srcdir)/../include \ -I$(srcdir)/../lib \ + $(JSON_C_CFLAGS) \ $(GLIB_CFLAGS) \ $(LIBREPORT_CFLAGS) \ -DPLUGINS_CONF_DIR=\"$(REPORT_PLUGINS_CONF_DIR)\" \ -D_GNU_SOURCE reporter_ureport_LDADD = \ + $(JSON_C_LIBS) \ ../lib/libreport.la \ ../lib/libreport-web.la endif diff --git a/src/plugins/reporter-ureport.c b/src/plugins/reporter-ureport.c new file mode 100644 index 0000000..d827c7d --- /dev/null +++ b/src/plugins/reporter-ureport.c @@ -0,0 +1,777 @@ +/* + Copyright (C) 2012 ABRT Team + Copyright (C) 2012 RedHat 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 +#include "internal_libreport.h" +#include "ureport.h" +#include "libreport_curl.h" + +#define CONF_FILE_PATH PLUGINS_CONF_DIR"/ureport.conf" + +#define REPORT_URL_SFX "reports/new/" +#define ATTACH_URL_SFX "reports/attach/" +#define BTHASH_URL_SFX "reports/bthash/" + +#define RHSM_CERT_PATH "/etc/pki/consumer/cert.pem" +#define RHSM_KEY_PATH "/etc/pki/consumer/key.pem" + +#define RHAP_PEM_DIR_PATH "/etc/pki/entitlement" +#define RHAP_ENT_DATA_BEGIN_TAG "-----BEGIN ENTITLEMENT DATA-----" +#define RHAP_ENT_DATA_END_TAG "-----END ENTITLEMENT DATA-----" +#define RHAP_SIG_DATA_BEGIN_TAG "-----BEGIN RSA SIGNATURE-----" +#define RHAP_SIG_DATA_END_TAG "-----END RSA SIGNATURE-----" + +#define VALUE_FROM_CONF(opt, var, tr) do { const char *value = getenv("uReport_"opt); \ + if (!value) { value = get_map_string_item_or_NULL(settings, opt); } if (value) { var = tr(value); } \ + } while(0) + +static char *puppet_config_print(const char *key) +{ + char *command = xasprintf("puppet config print %s", key); + char *result = run_in_shell_and_save_output(0, command, NULL, NULL); + free(command); + + /* run_in_shell_and_save_output always returns non-NULL */ + if (result[0] != '/') + goto error; + + char *newline = strchrnul(result, '\n'); + if (!newline) + goto error; + + *newline = '\0'; + return result; +error: + free(result); + error_msg_and_die("Unable to determine puppet %s path (puppet not installed?)", key); +} + +static void parse_client_auth_paths(struct ureport_server_config *config, const char *client_auth) +{ + if (client_auth == NULL) + return; + + if (strcmp(client_auth, "") == 0) + { + config->ur_client_cert = NULL; + config->ur_client_key = NULL; + log_notice("Not using client authentication"); + } + else if (strcmp(client_auth, "rhsm") == 0) + { + config->ur_client_cert = xstrdup(RHSM_CERT_PATH); + config->ur_client_key = xstrdup(RHSM_KEY_PATH); + } + else if (strcmp(client_auth, "rhsm-entitlement") == 0) + { + GList *certs = get_file_list(RHAP_PEM_DIR_PATH, "pem"); + if (g_list_length(certs) != 2) + { + log_notice(RHAP_PEM_DIR_PATH" does not contain unique cert-key files pair"); + log_notice("Not using client authentication"); + return; + } + + const char *cert = NULL; + const char *key = NULL; + + file_obj_t *fst = (file_obj_t *)certs->data; + file_obj_t *scn = (file_obj_t *)certs->next->data; + + if (strlen(fo_get_filename(fst)) < strlen(fo_get_filename(scn))) + { + cert = fo_get_filename(fst); + key = fo_get_filename(scn); + + config->ur_client_cert = xstrdup(fo_get_fullpath(fst)); + config->ur_client_key = xstrdup(fo_get_fullpath(scn)); + } + else + { + cert = fo_get_filename(scn); + key = fo_get_filename(fst); + + config->ur_client_cert = xstrdup(fo_get_fullpath(scn)); + config->ur_client_key = xstrdup(fo_get_fullpath(fst)); + } + + const bool iscomplement = prefixcmp(key, cert) != 0 || strcmp("-key", key + strlen(cert)) != 0; + g_list_free_full(certs, (GDestroyNotify)free_file_obj); + + if (iscomplement) + { + log_notice("Key file '%s' isn't complement to cert file '%s'", + config->ur_client_key, config->ur_client_cert); + log_notice("Not using client authentication"); + + free(config->ur_client_cert); + free(config->ur_client_key); + config->ur_client_cert = NULL; + config->ur_client_key = NULL; + + return; + } + + char *certdata = xmalloc_open_read_close(config->ur_client_cert, /*no size limit*/NULL); + if (certdata != NULL) + { + char *ent_data = xstrdup_between(certdata, + RHAP_ENT_DATA_BEGIN_TAG, RHAP_ENT_DATA_END_TAG); + + char *sig_data = xstrdup_between(certdata, + RHAP_SIG_DATA_BEGIN_TAG, RHAP_SIG_DATA_END_TAG); + + if (ent_data != NULL && sig_data != NULL) + { + ent_data = strremovech(ent_data, '\n'); + insert_map_string(config->ur_http_headers, + xstrdup("X-RH-Entitlement-Data"), + xasprintf(RHAP_ENT_DATA_BEGIN_TAG"%s"RHAP_ENT_DATA_END_TAG, ent_data)); + + sig_data = strremovech(sig_data, '\n'); + insert_map_string(config->ur_http_headers, + xstrdup("X-RH-Entitlement-Sig"), + xasprintf(RHAP_SIG_DATA_BEGIN_TAG"%s"RHAP_SIG_DATA_END_TAG, sig_data)); + } + else + { + log_notice("Cert file '%s' doesn't contain Entitlement and RSA Signature sections", config->ur_client_cert); + log_notice("Not using HTTP authentication headers"); + } + + free(sig_data); + free(ent_data); + free(certdata); + } + } + else if (strcmp(client_auth, "puppet") == 0) + { + config->ur_client_cert = puppet_config_print("hostcert"); + config->ur_client_key = puppet_config_print("hostprivkey"); + } + else + { + char *scratch = xstrdup(client_auth); + config->ur_client_cert = xstrdup(strtok(scratch, ":")); + config->ur_client_key = xstrdup(strtok(NULL, ":")); + free(scratch); + if (config->ur_client_cert == NULL || config->ur_client_key == NULL) + error_msg_and_die("Invalid client authentication specification"); + } + + if (config->ur_client_cert && config->ur_client_key) + { + log_notice("Using client certificate: %s", config->ur_client_cert); + log_notice("Using client private key: %s", config->ur_client_key); + } +} + +/* + * Loads uReport configuration from various sources. + * + * Replaces a value of an already configured option only if the + * option was found in a configuration source. + * + * @param config a server configuration to be populated + */ +static void load_ureport_server_config(struct ureport_server_config *config, map_string_t *settings) +{ + VALUE_FROM_CONF("URL", config->ur_url, (const char *)); + VALUE_FROM_CONF("SSLVerify", config->ur_ssl_verify, string_to_bool); + + bool include_auth = false; + VALUE_FROM_CONF("IncludeAuthData", include_auth, string_to_bool); + + if (include_auth) + { + const char *auth_items = NULL; + VALUE_FROM_CONF("AuthDataItems", auth_items, (const char *)); + config->ur_prefs.urp_auth_items = parse_list(auth_items); + + if (config->ur_prefs.urp_auth_items == NULL) + log_warning("IncludeAuthData set to 'yes' but AuthDataItems is empty."); + } + + const char *client_auth = NULL; + VALUE_FROM_CONF("SSLClientAuth", client_auth, (const char *)); + parse_client_auth_paths(config, client_auth); +} + +struct ureport_server_response { + bool is_error; + char *value; + char *message; + char *bthash; + GList *reported_to_list; + char *solution; +}; + +void free_ureport_server_response(struct ureport_server_response *resp) +{ + if (!resp) + return; + + free(resp->solution); + g_list_free_full(resp->reported_to_list, g_free); + free(resp->bthash); + free(resp->message); + free(resp->value); + free(resp); +} + +static char *parse_solution_from_json_list(struct json_object *list, GList **reported_to) +{ + json_object *list_elem, *struct_elem; + const char *cause, *note, *url; + struct strbuf *solution_buf = strbuf_new(); + + const unsigned length = json_object_array_length(list); + + const char *one_format = _("Your problem seems to be caused by %s\n\n%s\n"); + if (length > 1) + { + strbuf_append_str(solution_buf, _("Your problem seems to be caused by one of the following:\n")); + one_format = "\n* %s\n\n%s\n"; + } + + bool empty = true; + for (unsigned i = 0; i < length; ++i) + { + list_elem = json_object_array_get_idx(list, i); + if (!list_elem) + continue; + + if (!json_object_object_get_ex(list_elem, "cause", &struct_elem)) + continue; + + cause = json_object_get_string(struct_elem); + continue; + + if (!json_object_object_get_ex(list_elem, "note", &struct_elem)) + continue; + + note = json_object_get_string(struct_elem); + if (!note) + continue; + + empty = false; + strbuf_append_strf(solution_buf, one_format, cause, note); + + if (!json_object_object_get_ex(list_elem, "url", &struct_elem)) + continue; + + url = json_object_get_string(struct_elem); + if (url) + { + char *reported_to_line = xasprintf("%s: URL=%s", cause, url); + *reported_to = g_list_append(*reported_to, reported_to_line); + } + } + + if (empty) + { + strbuf_free(solution_buf); + return NULL; + } + + return strbuf_free_nobuf(solution_buf); +} + +/* reported_to json element should be a list of structures +{ "reporter": "Bugzilla", + "type": "url", + "value": "https://bugzilla.redhat.com/show_bug.cgi?id=XYZ" } */ +static GList *parse_reported_to_from_json_list(struct json_object *list) +{ + int i; + json_object *list_elem, *struct_elem; + const char *reporter, *value, *type; + char *reported_to_line, *prefix; + GList *result = NULL; + + for (i = 0; i < json_object_array_length(list); ++i) + { + prefix = NULL; + list_elem = json_object_array_get_idx(list, i); + if (!list_elem) + continue; + + if (!json_object_object_get_ex(list_elem, "reporter", &struct_elem)) + continue; + + reporter = json_object_get_string(struct_elem); + if (!reporter) + continue; + + if (!json_object_object_get_ex(list_elem, "value", &struct_elem)) + continue; + + value = json_object_get_string(struct_elem); + if (!value) + continue; + + if (!json_object_object_get_ex(list_elem, "type", &struct_elem)) + continue; + + type = json_object_get_string(struct_elem); + if (type) + { + if (strcasecmp("url", type) == 0) + prefix = xstrdup("URL="); + else if (strcasecmp("bthash", type) == 0) + prefix = xstrdup("BTHASH="); + } + + if (!prefix) + prefix = xstrdup(""); + + reported_to_line = xasprintf("%s: %s%s", reporter, prefix, value); + free(prefix); + + result = g_list_append(result, reported_to_line); + } + + return result; +} + +/* + * Reponse samples: + * {"error":"field 'foo' is required"} + * {"response":"true"} + * {"response":"false"} + */ +static struct ureport_server_response *ureport_server_parse_json(json_object *json) +{ + json_object *obj = NULL; + if (json_object_object_get_ex(json, "error", &obj)) + { + struct ureport_server_response *out_response = xzalloc(sizeof(*out_response)); + out_response->is_error = true; + /* + * Used to use json_object_to_json_string(obj), but it returns + * the string in quote marks (") - IOW, json-formatted string. + */ + out_response->value = xstrdup(json_object_get_string(obj)); + return out_response; + } + + if (json_object_object_get_ex(json, "result", &obj)) + { + struct ureport_server_response *out_response = xzalloc(sizeof(*out_response)); + out_response->value = xstrdup(json_object_get_string(obj)); + + json_object *message = NULL; + if (json_object_object_get_ex(json, "message", &message)) + out_response->message = xstrdup(json_object_get_string(message)); + + json_object *bthash = NULL; + if (json_object_object_get_ex(json, "bthash", &bthash)) + out_response->bthash = xstrdup(json_object_get_string(bthash)); + + json_object *reported_to_list = NULL; + if (json_object_object_get_ex(json, "reported_to", &reported_to_list)) + out_response->reported_to_list = parse_reported_to_from_json_list(reported_to_list); + + json_object *solutions = NULL; + if (json_object_object_get_ex(json, "solutions", &solutions)) + out_response->solution = parse_solution_from_json_list(solutions, &(out_response->reported_to_list)); + + return out_response; + } + + return NULL; +} + +static struct ureport_server_response *get_server_response(post_state_t *post_state, struct ureport_server_config *config) +{ + /* Previously, the condition here was (post_state->errmsg[0] != '\0') + * however when the server asks for optional client authentication and we do not have the certificates, + * then post_state->errmsg contains "NSS: client certificate not found (nickname not specified)" even though + * the request succeeded. + */ + if (post_state->curl_result != CURLE_OK) + { + error_msg(_("Failed to upload uReport to the server '%s' with curl: %s"), config->ur_url, post_state->errmsg); + return NULL; + } + + if (post_state->http_resp_code == 404) + { + error_msg(_("The URL '%s' does not exist (got error 404 from server)"), config->ur_url); + return NULL; + } + + if (post_state->http_resp_code == 500) + { + error_msg(_("The server at '%s' encountered an internal error (got error 500)"), config->ur_url); + return NULL; + } + + if (post_state->http_resp_code == 503) + { + error_msg(_("The server at '%s' currently can't handle the request (got error 503)"), config->ur_url); + return NULL; + } + + if (post_state->http_resp_code != 202 + && post_state->http_resp_code != 400 + && post_state->http_resp_code != 413) + { + /* can't print better error message */ + error_msg(_("Unexpected HTTP response from '%s': %d"), config->ur_url, post_state->http_resp_code); + log_notice("%s", post_state->body); + return NULL; + } + + json_object *const json = json_tokener_parse(post_state->body); + + if (is_error(json)) + { + error_msg(_("Unable to parse response from ureport server at '%s'"), config->ur_url); + log_notice("%s", post_state->body); + json_object_put(json); + return NULL; + } + + struct ureport_server_response *response = ureport_server_parse_json(json); + json_object_put(json); + + if (!response) + error_msg(_("The response from '%s' has invalid format"), config->ur_url); + else if ((post_state->http_resp_code == 202 && response->is_error) + || (post_state->http_resp_code != 202 && !response->is_error)) + { + /* HTTP CODE 202 means that call was successful but the response */ + /* has an error message */ + error_msg(_("Type mismatch has been detected in the response from '%s'"), config->ur_url); + } + + return response; +} + +typedef post_state_t *(*attach_handler)(const char *, void *, struct ureport_server_config *); + +static post_state_t *wrp_ureport_attach_rhbz(const char *ureport_hash, int *rhbz_bug, + struct ureport_server_config *config) +{ + return ureport_attach_rhbz(ureport_hash, *rhbz_bug, config); +} + +static bool perform_attach(struct ureport_server_config *config, const char *ureport_hash, + attach_handler handler, void *args) +{ + char *dest_url = concat_path_file(config->ur_url, ATTACH_URL_SFX); + const char *old_url = config->ur_url; + config->ur_url = dest_url; + post_state_t *post_state = handler(ureport_hash, args, config); + config->ur_url = old_url; + free(dest_url); + + struct ureport_server_response *resp = get_server_response(post_state, config); + free_post_state(post_state); + /* don't use str_bo_bool() because we require "true" string */ + const int result = !resp || resp->is_error || strcmp(resp->value,"true") != 0; + + if (resp && resp->is_error) + { + error_msg(_("The server at '%s' responded with an error: '%s'"), config->ur_url, resp->value); + } + + free_ureport_server_response(resp); + + return result; +} + +int main(int argc, char **argv) +{ + setlocale(LC_ALL, ""); +#if ENABLE_NLS + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); +#endif + + abrt_init(argv); + + struct ureport_server_config config = { + .ur_url = NULL, + .ur_ssl_verify = true, + .ur_client_cert = NULL, + .ur_client_key = NULL, + .ur_http_headers = NULL, + { + .urp_auth_items = NULL, + }, + }; + + config.ur_http_headers = new_map_string(); + + enum { + OPT_v = 1 << 0, + OPT_d = 1 << 1, + OPT_u = 1 << 2, + OPT_k = 1 << 3, + OPT_t = 1 << 4, + OPT_i = 1 << 5, + }; + + int ret = 1; /* "failure" (for now) */ + bool insecure = !config.ur_ssl_verify; + const char *conf_file = CONF_FILE_PATH; + const char *arg_server_url = NULL; + const char *client_auth = NULL; + GList *auth_items = NULL; + const char *dump_dir_path = "."; + const char *ureport_hash = NULL; + bool ureport_hash_from_rt = false; + int rhbz_bug = -1; + bool rhbz_bug_from_rt = false; + const char *email_address = NULL; + bool email_address_from_env = false; + struct dump_dir *dd = NULL; + struct options program_options[] = { + OPT__VERBOSE(&g_verbose), + OPT__DUMP_DIR(&dump_dir_path), + OPT_STRING('u', "url", &arg_server_url, "URL", _("Specify server URL")), + OPT_BOOL('k', "insecure", &insecure, + _("Allow insecure connection to ureport server")), + OPT_STRING('t', "auth", &client_auth, "SOURCE", _("Use client authentication")), + OPT_LIST('i', "auth_items", &auth_items, "AUTH_ITEMS", _("Additional files included in 'auth' key")), + OPT_STRING('c', NULL, &conf_file, "FILE", _("Configuration file")), + OPT_STRING('a', "attach", &ureport_hash, "BTHASH", + _("bthash of uReport to attach (conflicts with -A)")), + OPT_BOOL('A', "attach-rt", &ureport_hash_from_rt, + _("attach to a bthash from reported_to (conflicts with -a)")), + OPT_STRING('e', "email", &email_address, "EMAIL", + _("contact e-mail address (requires -a|-A, conflicts with -E)")), + OPT_BOOL('E', "email-env", &email_address_from_env, + _("contact e-mail address from environment or configuration file (requires -a|-A, conflicts with -e)")), + OPT_INTEGER('b', "bug-id", &rhbz_bug, + _("attach RHBZ bug (requires -a|-A, conflicts with -B)")), + OPT_BOOL('B', "bug-id-rt", &rhbz_bug_from_rt, + _("attach last RHBZ bug from reported_to (requires -a|-A, conflicts with -b)")), + OPT_END(), + }; + + const char *program_usage_string = _( + "& [-v] [-c FILE] [-u URL] [-k] [-t SOURCE] [-A -a bthash -B -b bug-id -E -e email] [-d DIR]\n" + "& [-v] [-c FILE] [-u URL] [-k] [-t SOURCE] [-i AUTH_ITEMS]\\\n" + " [-A -a bthash -B -b bug-id -E -e email] [-d DIR]\n" + "\n" + "Upload micro report or add an attachment to a micro report\n" + "\n" + "Reads the default configuration from "CONF_FILE_PATH + ); + + unsigned opts = parse_opts(argc, argv, program_options, program_usage_string); + + map_string_t *settings = new_map_string(); + load_conf_file(conf_file, settings, /*skip key w/o values:*/ false); + + load_ureport_server_config(&config, settings); + + if (opts & OPT_u) + config.ur_url = arg_server_url; + if (opts & OPT_k) + config.ur_ssl_verify = !insecure; + if (opts & OPT_t) + parse_client_auth_paths(&config, client_auth); + if (opts & OPT_i) + { + g_list_free_full(config.ur_prefs.urp_auth_items, free); + config.ur_prefs.urp_auth_items = auth_items; + } + + if (!config.ur_url) + error_msg_and_die("You need to specify server URL"); + + post_state_t *post_state = NULL; + + if (ureport_hash && ureport_hash_from_rt) + error_msg_and_die("You need to pass either -a bthash or -A"); + + if (rhbz_bug >= 0 && rhbz_bug_from_rt) + error_msg_and_die("You need to pass either -b bug-id or -B"); + + if (email_address && email_address_from_env) + error_msg_and_die("You need to pass either -e bthash or -E"); + + if (ureport_hash_from_rt || rhbz_bug_from_rt) + { + dd = dd_opendir(dump_dir_path, DD_OPEN_READONLY); + if (!dd) + xfunc_die(); + + if (ureport_hash_from_rt) + { + report_result_t *ureport_result = find_in_reported_to(dd, "uReport"); + + if (!ureport_result || !ureport_result->bthash) + error_msg_and_die(_("This problem does not have an uReport assigned.")); + + /* sorry, this will be leaked */ + ureport_hash = xstrdup(ureport_result->bthash); + + free_report_result(ureport_result); + } + + if (rhbz_bug_from_rt) + { + report_result_t *bz_result = find_in_reported_to(dd, "Bugzilla"); + + if (!bz_result || !bz_result->url) + error_msg_and_die(_("This problem has not been reported to Bugzilla.")); + + char *bugid_ptr = strstr(bz_result->url, "show_bug.cgi?id="); + if (!bugid_ptr) + error_msg_and_die(_("Unable to find bug ID in bugzilla URL '%s'"), bz_result->url); + bugid_ptr += strlen("show_bug.cgi?id="); + + /* we're just reading int, sscanf works fine */ + if (sscanf(bugid_ptr, "%d", &rhbz_bug) != 1) + error_msg_and_die(_("Unable to parse bug ID from bugzilla URL '%s'"), bz_result->url); + + free_report_result(bz_result); + } + + dd_close(dd); + } + + if (email_address_from_env) + { + VALUE_FROM_CONF("ContactEmail", email_address, (const char *)); + + if (!email_address) + error_msg_and_die(_("Neither environment variable 'uReport_ContactEmail' nor configuration option 'ContactEmail' is set")); + } + + if (ureport_hash) + { + if (rhbz_bug < 0 && !email_address) + error_msg_and_die(_("You need to specify bug ID, contact email or both")); + + if (rhbz_bug >= 0) + { + if (perform_attach(&config, ureport_hash, (attach_handler)wrp_ureport_attach_rhbz, (void *)&rhbz_bug)) + goto finalize; + } + + if (email_address) + { + if (perform_attach(&config, ureport_hash, (attach_handler)ureport_attach_email, (void *)email_address)) + goto finalize; + } + + ret = 0; + goto finalize; + } + if (!ureport_hash && (rhbz_bug >= 0 || email_address)) + error_msg_and_die(_("You need to specify bthash of the uReport to attach.")); + + /* -b, -a nor -r were specified - upload uReport from dump_dir */ + const char *server_url = config.ur_url; + char *dest_url = concat_path_file(config.ur_url, REPORT_URL_SFX); + config.ur_url = dest_url; + + char *json_ureport = ureport_from_dump_dir_ext(dump_dir_path, &(config.ur_prefs)); + if (!json_ureport) + { + error_msg(_("Not uploading an empty uReport")); + goto format_err; + } + + post_state = ureport_post(json_ureport, &config); + free(json_ureport); + + if (!post_state) + { + error_msg(_("Failed on submitting the problem")); + goto format_err; + } + + struct ureport_server_response *response = get_server_response(post_state, &config); + + if (!response) + goto format_err; + + if (!response->is_error) + { + log_notice("is known: %s", response->value); + ret = 0; /* "success" */ + + dd = dd_opendir(dump_dir_path, /* flags */ 0); + if (!dd) + xfunc_die(); + + if (response->bthash) + { + char *msg = xasprintf("uReport: BTHASH=%s", response->bthash); + add_reported_to(dd, msg); + free(msg); + + char *bthash_url = concat_path_file(server_url, BTHASH_URL_SFX); + msg = xasprintf("ABRT Server: URL=%s%s", bthash_url, response->bthash); + add_reported_to(dd, msg); + free(msg); + free(bthash_url); + } + + if (response->reported_to_list) + { + for (GList *e = response->reported_to_list; e; e = g_list_next(e)) + add_reported_to(dd, e->data); + } + + if (response->solution) + dd_save_text(dd, FILENAME_NOT_REPORTABLE, response->solution); + + dd_close(dd); + + /* If a reported problem is not known then emit NEEDMORE */ + if (strcmp("true", response->value) == 0) + { + log(_("This problem has already been reported.")); + if (response->message) + log(response->message); + + ret = EXIT_STOP_EVENT_RUN; + } + } + else + { + error_msg(_("Server responded with an error: '%s'"), response->value); + } + + free_ureport_server_response(response); + +format_err: + free_post_state(post_state); + free(dest_url); + +finalize: + if (config.ur_prefs.urp_auth_items != auth_items) + g_list_free_full(config.ur_prefs.urp_auth_items, free); + + free_map_string(config.ur_http_headers); + + free_map_string(settings); + free(config.ur_client_cert); + free(config.ur_client_key); + + return ret; +} diff --git a/src/plugins/ureport.c b/src/plugins/ureport.c deleted file mode 100644 index 9c69cad..0000000 --- a/src/plugins/ureport.c +++ /dev/null @@ -1,777 +0,0 @@ -/* - Copyright (C) 2012 ABRT Team - Copyright (C) 2012 RedHat 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 -#include "internal_libreport.h" -#include "ureport.h" -#include "libreport_curl.h" - -#define CONF_FILE_PATH PLUGINS_CONF_DIR"/ureport.conf" - -#define REPORT_URL_SFX "reports/new/" -#define ATTACH_URL_SFX "reports/attach/" -#define BTHASH_URL_SFX "reports/bthash/" - -#define RHSM_CERT_PATH "/etc/pki/consumer/cert.pem" -#define RHSM_KEY_PATH "/etc/pki/consumer/key.pem" - -#define RHAP_PEM_DIR_PATH "/etc/pki/entitlement" -#define RHAP_ENT_DATA_BEGIN_TAG "-----BEGIN ENTITLEMENT DATA-----" -#define RHAP_ENT_DATA_END_TAG "-----END ENTITLEMENT DATA-----" -#define RHAP_SIG_DATA_BEGIN_TAG "-----BEGIN RSA SIGNATURE-----" -#define RHAP_SIG_DATA_END_TAG "-----END RSA SIGNATURE-----" - -#define VALUE_FROM_CONF(opt, var, tr) do { const char *value = getenv("uReport_"opt); \ - if (!value) { value = get_map_string_item_or_NULL(settings, opt); } if (value) { var = tr(value); } \ - } while(0) - -static char *puppet_config_print(const char *key) -{ - char *command = xasprintf("puppet config print %s", key); - char *result = run_in_shell_and_save_output(0, command, NULL, NULL); - free(command); - - /* run_in_shell_and_save_output always returns non-NULL */ - if (result[0] != '/') - goto error; - - char *newline = strchrnul(result, '\n'); - if (!newline) - goto error; - - *newline = '\0'; - return result; -error: - free(result); - error_msg_and_die("Unable to determine puppet %s path (puppet not installed?)", key); -} - -static void parse_client_auth_paths(struct ureport_server_config *config, const char *client_auth) -{ - if (client_auth == NULL) - return; - - if (strcmp(client_auth, "") == 0) - { - config->ur_client_cert = NULL; - config->ur_client_key = NULL; - log_notice("Not using client authentication"); - } - else if (strcmp(client_auth, "rhsm") == 0) - { - config->ur_client_cert = xstrdup(RHSM_CERT_PATH); - config->ur_client_key = xstrdup(RHSM_KEY_PATH); - } - else if (strcmp(client_auth, "rhsm-entitlement") == 0) - { - GList *certs = get_file_list(RHAP_PEM_DIR_PATH, "pem"); - if (g_list_length(certs) != 2) - { - log_notice(RHAP_PEM_DIR_PATH" does not contain unique cert-key files pair"); - log_notice("Not using client authentication"); - return; - } - - const char *cert = NULL; - const char *key = NULL; - - file_obj_t *fst = (file_obj_t *)certs->data; - file_obj_t *scn = (file_obj_t *)certs->next->data; - - if (strlen(fo_get_filename(fst)) < strlen(fo_get_filename(scn))) - { - cert = fo_get_filename(fst); - key = fo_get_filename(scn); - - config->ur_client_cert = xstrdup(fo_get_fullpath(fst)); - config->ur_client_key = xstrdup(fo_get_fullpath(scn)); - } - else - { - cert = fo_get_filename(scn); - key = fo_get_filename(fst); - - config->ur_client_cert = xstrdup(fo_get_fullpath(scn)); - config->ur_client_key = xstrdup(fo_get_fullpath(fst)); - } - - const bool iscomplement = prefixcmp(key, cert) != 0 || strcmp("-key", key + strlen(cert)) != 0; - g_list_free_full(certs, (GDestroyNotify)free_file_obj); - - if (iscomplement) - { - log_notice("Key file '%s' isn't complement to cert file '%s'", - config->ur_client_key, config->ur_client_cert); - log_notice("Not using client authentication"); - - free(config->ur_client_cert); - free(config->ur_client_key); - config->ur_client_cert = NULL; - config->ur_client_key = NULL; - - return; - } - - char *certdata = xmalloc_open_read_close(config->ur_client_cert, /*no size limit*/NULL); - if (certdata != NULL) - { - char *ent_data = xstrdup_between(certdata, - RHAP_ENT_DATA_BEGIN_TAG, RHAP_ENT_DATA_END_TAG); - - char *sig_data = xstrdup_between(certdata, - RHAP_SIG_DATA_BEGIN_TAG, RHAP_SIG_DATA_END_TAG); - - if (ent_data != NULL && sig_data != NULL) - { - ent_data = strremovech(ent_data, '\n'); - insert_map_string(config->ur_http_headers, - xstrdup("X-RH-Entitlement-Data"), - xasprintf(RHAP_ENT_DATA_BEGIN_TAG"%s"RHAP_ENT_DATA_END_TAG, ent_data)); - - sig_data = strremovech(sig_data, '\n'); - insert_map_string(config->ur_http_headers, - xstrdup("X-RH-Entitlement-Sig"), - xasprintf(RHAP_SIG_DATA_BEGIN_TAG"%s"RHAP_SIG_DATA_END_TAG, sig_data)); - } - else - { - log_notice("Cert file '%s' doesn't contain Entitlement and RSA Signature sections", config->ur_client_cert); - log_notice("Not using HTTP authentication headers"); - } - - free(sig_data); - free(ent_data); - free(certdata); - } - } - else if (strcmp(client_auth, "puppet") == 0) - { - config->ur_client_cert = puppet_config_print("hostcert"); - config->ur_client_key = puppet_config_print("hostprivkey"); - } - else - { - char *scratch = xstrdup(client_auth); - config->ur_client_cert = xstrdup(strtok(scratch, ":")); - config->ur_client_key = xstrdup(strtok(NULL, ":")); - free(scratch); - if (config->ur_client_cert == NULL || config->ur_client_key == NULL) - error_msg_and_die("Invalid client authentication specification"); - } - - if (config->ur_client_cert && config->ur_client_key) - { - log_notice("Using client certificate: %s", config->ur_client_cert); - log_notice("Using client private key: %s", config->ur_client_key); - } -} - -/* - * Loads uReport configuration from various sources. - * - * Replaces a value of an already configured option only if the - * option was found in a configuration source. - * - * @param config a server configuration to be populated - */ -static void load_ureport_server_config(struct ureport_server_config *config, map_string_t *settings) -{ - VALUE_FROM_CONF("URL", config->ur_url, (const char *)); - VALUE_FROM_CONF("SSLVerify", config->ur_ssl_verify, string_to_bool); - - bool include_auth = false; - VALUE_FROM_CONF("IncludeAuthData", include_auth, string_to_bool); - - if (include_auth) - { - const char *auth_items = NULL; - VALUE_FROM_CONF("AuthDataItems", auth_items, (const char *)); - config->ur_prefs.urp_auth_items = parse_list(auth_items); - - if (config->ur_prefs.urp_auth_items == NULL) - log_warning("IncludeAuthData set to 'yes' but AuthDataItems is empty."); - } - - const char *client_auth = NULL; - VALUE_FROM_CONF("SSLClientAuth", client_auth, (const char *)); - parse_client_auth_paths(config, client_auth); -} - -struct ureport_server_response { - bool is_error; - char *value; - char *message; - char *bthash; - GList *reported_to_list; - char *solution; -}; - -void free_ureport_server_response(struct ureport_server_response *resp) -{ - if (!resp) - return; - - free(resp->solution); - g_list_free_full(resp->reported_to_list, g_free); - free(resp->bthash); - free(resp->message); - free(resp->value); - free(resp); -} - -static char *parse_solution_from_json_list(struct json_object *list, GList **reported_to) -{ - json_object *list_elem, *struct_elem; - const char *cause, *note, *url; - struct strbuf *solution_buf = strbuf_new(); - - const unsigned length = json_object_array_length(list); - - const char *one_format = _("Your problem seems to be caused by %s\n\n%s\n"); - if (length > 1) - { - strbuf_append_str(solution_buf, _("Your problem seems to be caused by one of the following:\n")); - one_format = "\n* %s\n\n%s\n"; - } - - bool empty = true; - for (unsigned i = 0; i < length; ++i) - { - list_elem = json_object_array_get_idx(list, i); - if (!list_elem) - continue; - - if (!json_object_object_get_ex(list_elem, "cause", &struct_elem)) - continue; - - cause = json_object_get_string(struct_elem); - continue; - - if (!json_object_object_get_ex(list_elem, "note", &struct_elem)) - continue; - - note = json_object_get_string(struct_elem); - if (!note) - continue; - - empty = false; - strbuf_append_strf(solution_buf, one_format, cause, note); - - if (!json_object_object_get_ex(list_elem, "url", &struct_elem)) - continue; - - url = json_object_get_string(struct_elem); - if (url) - { - char *reported_to_line = xasprintf("%s: URL=%s", cause, url); - *reported_to = g_list_append(*reported_to, reported_to_line); - } - } - - if (empty) - { - strbuf_free(solution_buf); - return NULL; - } - - return strbuf_free_nobuf(solution_buf); -} - -/* reported_to json element should be a list of structures -{ "reporter": "Bugzilla", - "type": "url", - "value": "https://bugzilla.redhat.com/show_bug.cgi?id=XYZ" } */ -static GList *parse_reported_to_from_json_list(struct json_object *list) -{ - int i; - json_object *list_elem, *struct_elem; - const char *reporter, *value, *type; - char *reported_to_line, *prefix; - GList *result = NULL; - - for (i = 0; i < json_object_array_length(list); ++i) - { - prefix = NULL; - list_elem = json_object_array_get_idx(list, i); - if (!list_elem) - continue; - - if (!json_object_object_get_ex(list_elem, "reporter", &struct_elem)) - continue; - - reporter = json_object_get_string(struct_elem); - if (!reporter) - continue; - - if (!json_object_object_get_ex(list_elem, "value", &struct_elem)) - continue; - - value = json_object_get_string(struct_elem); - if (!value) - continue; - - if (!json_object_object_get_ex(list_elem, "type", &struct_elem)) - continue; - - type = json_object_get_string(struct_elem); - if (type) - { - if (strcasecmp("url", type) == 0) - prefix = xstrdup("URL="); - else if (strcasecmp("bthash", type) == 0) - prefix = xstrdup("BTHASH="); - } - - if (!prefix) - prefix = xstrdup(""); - - reported_to_line = xasprintf("%s: %s%s", reporter, prefix, value); - free(prefix); - - result = g_list_append(result, reported_to_line); - } - - return result; -} - -/* - * Reponse samples: - * {"error":"field 'foo' is required"} - * {"response":"true"} - * {"response":"false"} - */ -static struct ureport_server_response *ureport_server_parse_json(json_object *json) -{ - json_object *obj = NULL; - if (json_object_object_get_ex(json, "error", &obj)) - { - struct ureport_server_response *out_response = xzalloc(sizeof(*out_response)); - out_response->is_error = true; - /* - * Used to use json_object_to_json_string(obj), but it returns - * the string in quote marks (") - IOW, json-formatted string. - */ - out_response->value = xstrdup(json_object_get_string(obj)); - return out_response; - } - - if (json_object_object_get_ex(json, "result", &obj)) - { - struct ureport_server_response *out_response = xzalloc(sizeof(*out_response)); - out_response->value = xstrdup(json_object_get_string(obj)); - - json_object *message = NULL; - if (json_object_object_get_ex(json, "message", &message)) - out_response->message = xstrdup(json_object_get_string(message)); - - json_object *bthash = NULL; - if (json_object_object_get_ex(json, "bthash", &bthash)) - out_response->bthash = xstrdup(json_object_get_string(bthash)); - - json_object *reported_to_list = NULL; - if (json_object_object_get_ex(json, "reported_to", &reported_to_list)) - out_response->reported_to_list = parse_reported_to_from_json_list(reported_to_list); - - json_object *solutions = NULL; - if (json_object_object_get_ex(json, "solutions", &solutions)) - out_response->solution = parse_solution_from_json_list(solutions, &(out_response->reported_to_list)); - - return out_response; - } - - return NULL; -} - -static struct ureport_server_response *get_server_response(post_state_t *post_state, struct ureport_server_config *config) -{ - /* Previously, the condition here was (post_state->errmsg[0] != '\0') - * however when the server asks for optional client authentication and we do not have the certificates, - * then post_state->errmsg contains "NSS: client certificate not found (nickname not specified)" even though - * the request succeeded. - */ - if (post_state->curl_result != CURLE_OK) - { - error_msg(_("Failed to upload uReport to the server '%s' with curl: %s"), config->ur_url, post_state->errmsg); - return NULL; - } - - if (post_state->http_resp_code == 404) - { - error_msg(_("The URL '%s' does not exist (got error 404 from server)"), config->ur_url); - return NULL; - } - - if (post_state->http_resp_code == 500) - { - error_msg(_("The server at '%s' encountered an internal error (got error 500)"), config->ur_url); - return NULL; - } - - if (post_state->http_resp_code == 503) - { - error_msg(_("The server at '%s' currently can't handle the request (got error 503)"), config->ur_url); - return NULL; - } - - if (post_state->http_resp_code != 202 - && post_state->http_resp_code != 400 - && post_state->http_resp_code != 413) - { - /* can't print better error message */ - error_msg(_("Unexpected HTTP response from '%s': %d"), config->ur_url, post_state->http_resp_code); - log_notice("%s", post_state->body); - return NULL; - } - - json_object *const json = json_tokener_parse(post_state->body); - - if (is_error(json)) - { - error_msg(_("Unable to parse response from ureport server at '%s'"), config->ur_url); - log_notice("%s", post_state->body); - json_object_put(json); - return NULL; - } - - struct ureport_server_response *response = ureport_server_parse_json(json); - json_object_put(json); - - if (!response) - error_msg(_("The response from '%s' has invalid format"), config->ur_url); - else if ((post_state->http_resp_code == 202 && response->is_error) - || (post_state->http_resp_code != 202 && !response->is_error)) - { - /* HTTP CODE 202 means that call was successful but the response */ - /* has an error message */ - error_msg(_("Type mismatch has been detected in the response from '%s'"), config->ur_url); - } - - return response; -} - -typedef post_state_t *(*attach_handler)(const char *, void *, struct ureport_server_config *); - -static post_state_t *wrp_ureport_attach_rhbz(const char *ureport_hash, int *rhbz_bug, - struct ureport_server_config *config) -{ - return ureport_attach_rhbz(ureport_hash, *rhbz_bug, config); -} - -static bool perform_attach(struct ureport_server_config *config, const char *ureport_hash, - attach_handler handler, void *args) -{ - char *dest_url = concat_path_file(config->ur_url, ATTACH_URL_SFX); - const char *old_url = config->ur_url; - config->ur_url = dest_url; - post_state_t *post_state = handler(ureport_hash, args, config); - config->ur_url = old_url; - free(dest_url); - - struct ureport_server_response *resp = get_server_response(post_state, config); - free_post_state(post_state); - /* don't use str_bo_bool() because we require "true" string */ - const int result = !resp || resp->is_error || strcmp(resp->value,"true") != 0; - - if (resp && resp->is_error) - { - error_msg(_("The server at '%s' responded with an error: '%s'"), config->ur_url, resp->value); - } - - free_ureport_server_response(resp); - - return result; -} - -int main(int argc, char **argv) -{ - setlocale(LC_ALL, ""); -#if ENABLE_NLS - bindtextdomain(PACKAGE, LOCALEDIR); - textdomain(PACKAGE); -#endif - - abrt_init(argv); - - struct ureport_server_config config = { - .ur_url = NULL, - .ur_ssl_verify = true, - .ur_client_cert = NULL, - .ur_client_key = NULL, - .ur_http_headers = NULL, - { - .urp_auth_items = NULL, - }, - }; - - config.ur_http_headers = new_map_string(); - - enum { - OPT_v = 1 << 0, - OPT_d = 1 << 1, - OPT_u = 1 << 2, - OPT_k = 1 << 3, - OPT_t = 1 << 4, - OPT_i = 1 << 5, - }; - - int ret = 1; /* "failure" (for now) */ - bool insecure = !config.ur_ssl_verify; - const char *conf_file = CONF_FILE_PATH; - const char *arg_server_url = NULL; - const char *client_auth = NULL; - GList *auth_items = NULL; - const char *dump_dir_path = "."; - const char *ureport_hash = NULL; - bool ureport_hash_from_rt = false; - int rhbz_bug = -1; - bool rhbz_bug_from_rt = false; - const char *email_address = NULL; - bool email_address_from_env = false; - struct dump_dir *dd = NULL; - struct options program_options[] = { - OPT__VERBOSE(&g_verbose), - OPT__DUMP_DIR(&dump_dir_path), - OPT_STRING('u', "url", &arg_server_url, "URL", _("Specify server URL")), - OPT_BOOL('k', "insecure", &insecure, - _("Allow insecure connection to ureport server")), - OPT_STRING('t', "auth", &client_auth, "SOURCE", _("Use client authentication")), - OPT_LIST('i', "auth_items", &auth_items, "AUTH_ITEMS", _("Additional files included in 'auth' key")), - OPT_STRING('c', NULL, &conf_file, "FILE", _("Configuration file")), - OPT_STRING('a', "attach", &ureport_hash, "BTHASH", - _("bthash of uReport to attach (conflicts with -A)")), - OPT_BOOL('A', "attach-rt", &ureport_hash_from_rt, - _("attach to a bthash from reported_to (conflicts with -a)")), - OPT_STRING('e', "email", &email_address, "EMAIL", - _("contact e-mail address (requires -a|-A, conflicts with -E)")), - OPT_BOOL('E', "email-env", &email_address_from_env, - _("contact e-mail address from environment or configuration file (requires -a|-A, conflicts with -e)")), - OPT_INTEGER('b', "bug-id", &rhbz_bug, - _("attach RHBZ bug (requires -a|-A, conflicts with -B)")), - OPT_BOOL('B', "bug-id-rt", &rhbz_bug_from_rt, - _("attach last RHBZ bug from reported_to (requires -a|-A, conflicts with -b)")), - OPT_END(), - }; - - const char *program_usage_string = _( - "& [-v] [-c FILE] [-u URL] [-k] [-t SOURCE] [-A -a bthash -B -b bug-id -E -e email] [-d DIR]\n" - "& [-v] [-c FILE] [-u URL] [-k] [-t SOURCE] [-i AUTH_ITEMS]\\\n" - " [-A -a bthash -B -b bug-id -E -e email] [-d DIR]\n" - "\n" - "Upload micro report or add an attachment to a micro report\n" - "\n" - "Reads the default configuration from "CONF_FILE_PATH - ); - - unsigned opts = parse_opts(argc, argv, program_options, program_usage_string); - - map_string_t *settings = new_map_string(); - load_conf_file(conf_file, settings, /*skip key w/o values:*/ false); - - load_ureport_server_config(&config, settings); - - if (opts & OPT_u) - config.ur_url = arg_server_url; - if (opts & OPT_k) - config.ur_ssl_verify = !insecure; - if (opts & OPT_t) - parse_client_auth_paths(&config, client_auth); - if (opts & OPT_i) - { - g_list_free_full(config.ur_prefs.urp_auth_items, free); - config.ur_prefs.urp_auth_items = auth_items; - } - - if (!config.ur_url) - error_msg_and_die("You need to specify server URL"); - - post_state_t *post_state = NULL; - - if (ureport_hash && ureport_hash_from_rt) - error_msg_and_die("You need to pass either -a bthash or -A"); - - if (rhbz_bug >= 0 && rhbz_bug_from_rt) - error_msg_and_die("You need to pass either -b bug-id or -B"); - - if (email_address && email_address_from_env) - error_msg_and_die("You need to pass either -e bthash or -E"); - - if (ureport_hash_from_rt || rhbz_bug_from_rt) - { - dd = dd_opendir(dump_dir_path, DD_OPEN_READONLY); - if (!dd) - xfunc_die(); - - if (ureport_hash_from_rt) - { - report_result_t *ureport_result = find_in_reported_to(dd, "uReport"); - - if (!ureport_result || !ureport_result->bthash) - error_msg_and_die(_("This problem does not have an uReport assigned.")); - - /* sorry, this will be leaked */ - ureport_hash = xstrdup(ureport_result->bthash); - - free_report_result(ureport_result); - } - - if (rhbz_bug_from_rt) - { - report_result_t *bz_result = find_in_reported_to(dd, "Bugzilla"); - - if (!bz_result || !bz_result->url) - error_msg_and_die(_("This problem has not been reported to Bugzilla.")); - - char *bugid_ptr = strstr(bz_result->url, "show_bug.cgi?id="); - if (!bugid_ptr) - error_msg_and_die(_("Unable to find bug ID in bugzilla URL '%s'"), bz_result->url); - bugid_ptr += strlen("show_bug.cgi?id="); - - /* we're just reading int, sscanf works fine */ - if (sscanf(bugid_ptr, "%d", &rhbz_bug) != 1) - error_msg_and_die(_("Unable to parse bug ID from bugzilla URL '%s'"), bz_result->url); - - free_report_result(bz_result); - } - - dd_close(dd); - } - - if (email_address_from_env) - { - VALUE_FROM_CONF("ContactEmail", email_address, (const char *)); - - if (!email_address) - error_msg_and_die(_("Neither environment variable 'uReport_ContactEmail' nor configuration option 'ContactEmail' is set")); - } - - if (ureport_hash) - { - if (rhbz_bug < 0 && !email_address) - error_msg_and_die(_("You need to specify bug ID, contact email or both")); - - if (rhbz_bug >= 0) - { - if (perform_attach(&config, ureport_hash, (attach_handler)wrp_ureport_attach_rhbz, (void *)&rhbz_bug)) - goto finalize; - } - - if (email_address) - { - if (perform_attach(&config, ureport_hash, (attach_handler)ureport_attach_email, (void *)email_address)) - goto finalize; - } - - ret = 0; - goto finalize; - } - if (!ureport_hash && (rhbz_bug >= 0 || email_address)) - error_msg_and_die(_("You need to specify bthash of the uReport to attach.")); - - /* -b, -a nor -r were specified - upload uReport from dump_dir */ - const char *server_url = config.ur_url; - char *dest_url = concat_path_file(config.ur_url, REPORT_URL_SFX); - config.ur_url = dest_url; - - char *json_ureport = ureport_from_dump_dir_ext(dump_dir_path, &(config.ur_prefs)); - if (!json_ureport) - { - error_msg(_("Not uploading an empty uReport")); - goto format_err; - } - - post_state = post_ureport(json_ureport, &config); - free(json_ureport); - - if (!post_state) - { - error_msg(_("Failed on submitting the problem")); - goto format_err; - } - - struct ureport_server_response *response = get_server_response(post_state, &config); - - if (!response) - goto format_err; - - if (!response->is_error) - { - log_notice("is known: %s", response->value); - ret = 0; /* "success" */ - - dd = dd_opendir(dump_dir_path, /* flags */ 0); - if (!dd) - xfunc_die(); - - if (response->bthash) - { - char *msg = xasprintf("uReport: BTHASH=%s", response->bthash); - add_reported_to(dd, msg); - free(msg); - - char *bthash_url = concat_path_file(server_url, BTHASH_URL_SFX); - msg = xasprintf("ABRT Server: URL=%s%s", bthash_url, response->bthash); - add_reported_to(dd, msg); - free(msg); - free(bthash_url); - } - - if (response->reported_to_list) - { - for (GList *e = response->reported_to_list; e; e = g_list_next(e)) - add_reported_to(dd, e->data); - } - - if (response->solution) - dd_save_text(dd, FILENAME_NOT_REPORTABLE, response->solution); - - dd_close(dd); - - /* If a reported problem is not known then emit NEEDMORE */ - if (strcmp("true", response->value) == 0) - { - log(_("This problem has already been reported.")); - if (response->message) - log(response->message); - - ret = EXIT_STOP_EVENT_RUN; - } - } - else - { - error_msg(_("Server responded with an error: '%s'"), response->value); - } - - free_ureport_server_response(response); - -format_err: - free_post_state(post_state); - free(dest_url); - -finalize: - if (config.ur_prefs.urp_auth_items != auth_items) - g_list_free_full(config.ur_prefs.urp_auth_items, free); - - free_map_string(config.ur_http_headers); - - free_map_string(settings); - free(config.ur_client_cert); - free(config.ur_client_key); - - return ret; -} -- 1.8.3.1