/* 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 "client.h" #include "ureport.h" #include "libreport_curl.h" #define DESTROYED_POINTER (void *)0xdeadbeef #define BTHASH_URL_SFX "reports/bthash/" 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); } void ureport_server_config_set_url(struct ureport_server_config *config, char *server_url) { free(config->ur_url); config->ur_url = server_url; } void ureport_server_config_set_client_auth(struct ureport_server_config *config, const char *client_auth) { if (client_auth == NULL) return; if (strcmp(client_auth, "") == 0) { free(config->ur_client_cert); config->ur_client_cert = NULL; free(config->ur_client_key); config->ur_client_key = NULL; log_notice("Not using client authentication"); } 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); free(config->ur_username); config->ur_username = NULL; free(config->ur_password); config->ur_password = NULL; } } void ureport_server_config_set_basic_auth(struct ureport_server_config *config, const char *login, const char *password) { ureport_server_config_set_client_auth(config, ""); free(config->ur_username); config->ur_username = xstrdup(login); free(config->ur_password); config->ur_password = xstrdup(password); } void ureport_server_config_load_basic_auth(struct ureport_server_config *config, const char *http_auth_pref) { if (http_auth_pref == NULL) return; map_string_t *settings = NULL; char *tmp_password = NULL; char *tmp_username = NULL; const char *username = NULL; const char *password = NULL; username = tmp_username = xstrdup(http_auth_pref); password = strchr(tmp_username, ':'); if (password != NULL) /* It is "char *", see strchr() few lines above. */ *((char *)(password++)) = '\0'; if (password == NULL) { char *message = xasprintf("Please provide uReport server password for user '%s':", username); password = tmp_password = ask_password(message); free(message); if (strcmp(password, "") == 0) error_msg_and_die("Cannot continue without uReport server password!"); } ureport_server_config_set_basic_auth(config, username, password); free(tmp_password); free(tmp_username); free_map_string(settings); } void ureport_server_config_load(struct ureport_server_config *config, map_string_t *settings) { UREPORT_OPTION_VALUE_FROM_CONF(settings, "URL", config->ur_url, xstrdup); UREPORT_OPTION_VALUE_FROM_CONF(settings, "SSLVerify", config->ur_ssl_verify, string_to_bool); const char *http_auth_pref = NULL; UREPORT_OPTION_VALUE_FROM_CONF(settings, "HTTPAuth", http_auth_pref, (const char *)); ureport_server_config_load_basic_auth(config, http_auth_pref); const char *client_auth = NULL; if (http_auth_pref == NULL) { UREPORT_OPTION_VALUE_FROM_CONF(settings, "SSLClientAuth", client_auth, (const char *)); ureport_server_config_set_client_auth(config, client_auth); } /* If SSLClientAuth is configured, include the auth items by default. */ bool include_auth = config->ur_client_cert != NULL || config->ur_username != NULL; UREPORT_OPTION_VALUE_FROM_CONF(settings, "IncludeAuthData", include_auth, string_to_bool); if (include_auth) { const char *auth_items = NULL; UREPORT_OPTION_VALUE_FROM_CONF(settings, "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."); } } void ureport_server_config_init(struct ureport_server_config *config) { config->ur_url = NULL; config->ur_ssl_verify = true; config->ur_client_cert = NULL; config->ur_client_key = NULL; config->ur_cert_authority_cert = NULL; config->ur_username = NULL; config->ur_password = NULL; config->ur_http_headers = new_map_string(); config->ur_prefs.urp_auth_items = NULL; config->ur_prefs.urp_flags = 0; } void ureport_server_config_destroy(struct ureport_server_config *config) { free(config->ur_url); config->ur_url = DESTROYED_POINTER; free(config->ur_client_cert); config->ur_client_cert = DESTROYED_POINTER; free(config->ur_client_key); config->ur_client_key = DESTROYED_POINTER; free(config->ur_cert_authority_cert); config->ur_cert_authority_cert = DESTROYED_POINTER; free(config->ur_username); config->ur_username = DESTROYED_POINTER; free(config->ur_password); config->ur_password = DESTROYED_POINTER; g_list_free_full(config->ur_prefs.urp_auth_items, free); config->ur_prefs.urp_auth_items = DESTROYED_POINTER; free_map_string(config->ur_http_headers); config->ur_http_headers = DESTROYED_POINTER; } void ureport_server_response_free(struct ureport_server_response *resp) { if (!resp) return; free(resp->urr_solution); resp->urr_solution = DESTROYED_POINTER; g_list_free_full(resp->urr_reported_to_list, g_free); resp->urr_reported_to_list = DESTROYED_POINTER; free(resp->urr_bthash); resp->urr_bthash = DESTROYED_POINTER; free(resp->urr_message); resp->urr_message = DESTROYED_POINTER; free(resp->urr_value); resp->urr_value = DESTROYED_POINTER; 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); if (!cause) 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->urr_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->urr_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->urr_value = xstrdup(json_object_get_string(obj)); json_object *message = NULL; if (json_object_object_get_ex(json, "message", &message)) out_response->urr_message = xstrdup(json_object_get_string(message)); json_object *bthash = NULL; if (json_object_object_get_ex(json, "bthash", &bthash)) out_response->urr_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->urr_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->urr_solution = parse_solution_from_json_list(solutions, &(out_response->urr_reported_to_list)); return out_response; } return NULL; } struct ureport_server_response * ureport_server_response_from_reply(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) { if (strcmp(post_state->errmsg, "") != 0) error_msg(_("Failed to upload uReport to the server '%s' with curl: %s"), config->ur_url, post_state->errmsg); else error_msg(_("Failed to upload uReport to the server '%s'"), config->ur_url); if (post_state->curl_error_msg != NULL && strcmp(post_state->curl_error_msg, "") != 0) error_msg(_("Error: %s"), post_state->curl_error_msg); 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 (json == NULL) { 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->urr_is_error) || (post_state->http_resp_code != 202 && !response->urr_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; } bool ureport_server_response_save_in_dump_dir(struct ureport_server_response *resp, const char *dump_dir_path, struct ureport_server_config *config) { struct dump_dir *dd = dd_opendir(dump_dir_path, /* flags */ 0); if (!dd) return false; if (resp->urr_bthash) { { report_result_t rr = { .label = (char *)"uReport" }; rr.bthash = resp->urr_bthash; add_reported_to_entry(dd, &rr); } { report_result_t rr = { .label = (char *)"ABRT Server" }; rr.url = ureport_server_response_get_report_url(resp, config); add_reported_to_entry(dd, &rr); free(rr.url); } } if (resp->urr_reported_to_list) { for (GList *e = resp->urr_reported_to_list; e; e = g_list_next(e)) add_reported_to(dd, e->data); } if (resp->urr_solution) dd_save_text(dd, FILENAME_NOT_REPORTABLE, resp->urr_solution); dd_close(dd); return true; } char * ureport_server_response_get_report_url(struct ureport_server_response *resp, struct ureport_server_config *config) { char *bthash_url = concat_path_file(config->ur_url, BTHASH_URL_SFX); char *report_url = concat_path_file(bthash_url, resp->urr_bthash); free(bthash_url); return report_url; } 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) { if (NULL == preferences || !(preferences->urp_flags & UREPORT_PREF_FLAG_RETURN_ON_FAILURE)) error_msg_and_die("%s", error_message); log_notice("%s", error_message); return NULL; } 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); free(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); } struct post_state * ureport_do_post(const char *json, struct ureport_server_config *config, const char *url_sfx) { 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; post_state->cert_authority_cert_path = config->ur_cert_authority_cert; } else if (config->ur_username && config->ur_password) { post_state->username = config->ur_username; post_state->password = config->ur_password; } 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; } char *dest_url = concat_path_file(config->ur_url, url_sfx); post_string_as_form_data(post_state, dest_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, dest_url, "application/json", (const char **)headers, json); } free(dest_url); for (unsigned i = size_map_string(config->ur_http_headers); i != 0; --i) free(headers[i + 1]); free(headers); return post_state; } struct ureport_server_response * ureport_submit(const char *json, struct ureport_server_config *config) { struct post_state *post_state = ureport_do_post(json, config, UREPORT_SUBMIT_ACTION); if (post_state == NULL) { error_msg(_("Failed on submitting the problem")); return NULL; } struct ureport_server_response *resp = ureport_server_response_from_reply(post_state, config); free(post_state); return resp; } char * ureport_json_attachment_new(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; } bool ureport_attach_string(const char *bthash, const char *type, const char *data, struct ureport_server_config *config) { char *json = ureport_json_attachment_new(bthash, type, data); post_state_t *post_state = ureport_do_post(json, config, UREPORT_ATTACH_ACTION); free(json); struct ureport_server_response *resp = ureport_server_response_from_reply(post_state, config); free_post_state(post_state); /* don't use str_bo_bool() because we require "true" string */ const int result = !resp || resp->urr_is_error || strcmp(resp->urr_value, "true") != 0; if (resp && resp->urr_is_error) error_msg(_("The server at '%s' responded with an error: '%s'"), config->ur_url, resp->urr_value); ureport_server_response_free(resp); return result; } bool ureport_attach_int(const char *bthash, const char *type, int data, struct ureport_server_config *config) { char *data_str = xasprintf("%d", data); const bool result = ureport_attach_string(bthash, type, data_str, config); free(data_str); return result; }