diff options
Diffstat (limited to 'lib/Plugins/Catcut.cpp')
-rw-r--r-- | lib/Plugins/Catcut.cpp | 597 |
1 files changed, 402 insertions, 195 deletions
diff --git a/lib/Plugins/Catcut.cpp b/lib/Plugins/Catcut.cpp index 6bb44a9d..e6d16b4f 100644 --- a/lib/Plugins/Catcut.cpp +++ b/lib/Plugins/Catcut.cpp @@ -1,7 +1,8 @@ #include <xmlrpc-c/base.h> #include <xmlrpc-c/client.h> - +#include <curl/curl.h> #include "abrtlib.h" +#include "abrt_xmlrpc.h" #include "Catcut.h" #include "CrashTypes.h" #include "DebugDump.h" @@ -13,108 +14,8 @@ using namespace std; -static xmlrpc_env env; -static xmlrpc_client* client = NULL; -static struct xmlrpc_clientparms clientParms; -static struct xmlrpc_curl_xportparms curlParms; -static xmlrpc_server_info* server_info = NULL; - - -static string login(const char* login, const char* passwd); -//static void logout(); -static void new_xmlrpc_client(const char* url, bool no_ssl_verify); -static void destroy_xmlrpc_client(); -static void create_new_bug_description(const map_crash_report_t& pCrashReport, string& pDescription); -static void get_product_and_version(const string& pRelease, - string& pProduct, - string& pVersion); - - -static void throw_if_xml_fault_occurred() -{ - if (env.fault_occurred) - { - string errmsg = ssprintf("XML-RPC Fault: %s(%d)", env.fault_string, env.fault_code); - error_msg("%s", errmsg.c_str()); // show error in daemon log - throw CABRTException(EXCEP_PLUGIN, errmsg); - } -} - -static void new_xmlrpc_client(const char* url, bool no_ssl_verify) -{ - xmlrpc_env_init(&env); - - /* This should be done at program startup, once. - * We do it in abrtd's main */ - /* xmlrpc_client_setup_global_const(&env); */ - - curlParms.network_interface = NULL; - curlParms.no_ssl_verifypeer = no_ssl_verify; - curlParms.no_ssl_verifyhost = no_ssl_verify; -#ifdef VERSION - curlParms.user_agent = PACKAGE_NAME"/"VERSION; -#else - curlParms.user_agent = "abrt"; -#endif - - clientParms.transport = "curl"; - clientParms.transportparmsP = &curlParms; - clientParms.transportparm_size = XMLRPC_CXPSIZE(user_agent); - - xmlrpc_client_create(&env, XMLRPC_CLIENT_NO_FLAGS, PACKAGE_NAME, VERSION, &clientParms, XMLRPC_CPSIZE(transportparm_size), - &client); - throw_if_xml_fault_occurred(); - - server_info = xmlrpc_server_info_new(&env, url); - throw_if_xml_fault_occurred(); -} - -static void destroy_xmlrpc_client() -{ - xmlrpc_server_info_free(server_info); - xmlrpc_env_clean(&env); - xmlrpc_client_destroy(client); -} - -static string login(const char* login, const char* passwd) -{ - xmlrpc_value* param = xmlrpc_build_value(&env, "(ss)", login, passwd); - throw_if_xml_fault_occurred(); - - xmlrpc_value* result; - xmlrpc_client_call2(&env, client, server_info, "Catcut.auth", param, &result); - throw_if_xml_fault_occurred(); - xmlrpc_DECREF(param); - - xmlrpc_value *cookie_xml; - const char *cookie; - string cookie_str; - xmlrpc_struct_find_value(&env, result, "cookie", &cookie_xml); - throw_if_xml_fault_occurred(); - xmlrpc_read_string(&env, cookie_xml, &cookie); - throw_if_xml_fault_occurred(); - cookie_str = cookie; - /* xmlrpc_read_string returns *malloc'ed ptr*. - * doc is not very clear on it, but I looked in xmlrpc sources. */ - free((void*)cookie); - xmlrpc_DECREF(cookie_xml); - - xmlrpc_DECREF(result); - - return cookie_str; -} - -// catcut does not have it (yet?) -//static void logout() -//{ -// xmlrpc_value* param = xmlrpc_build_value(&env, "(s)", ""); -// throw_if_xml_fault_occurred(); -// -// xmlrpc_value* result = NULL; /* paranoia */ -// xmlrpc_client_call2(&env, client, server_info, "User.logout", param, &result); -// throw_if_xml_fault_occurred(); -//} +//TODO: move to make_descr.cpp static void create_new_bug_description(const map_crash_report_t& pCrashReport, string& pDescription) { string howToReproduce; @@ -162,46 +63,258 @@ static void create_new_bug_description(const map_crash_report_t& pCrashReport, s } else if (it->second[CD_TYPE] == CD_BIN) { - string msg = ssprintf(_("Binary file %s will not be reported."), it->first.c_str()); - warn_client(msg); - //update_client(_("Binary file ")+it->first+_(" will not be reported.")); + error_msg(_("Binary file %s will not be reported"), it->first.c_str()); } } } -static void get_product_and_version(const string& pRelease, - string& pProduct, - string& pVersion) +static int +put_stream(const char *pURL, FILE* f, size_t content_length) { - if (pRelease.find("Rawhide") != string::npos) + CURL* curl = curl_easy_init(); + if (!curl) { - pProduct = "Fedora"; - pVersion = "rawhide"; + throw CABRTException(EXCEP_PLUGIN, "put_stream: Curl library error."); + } + /* enable uploading */ + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); + /* specify target */ + curl_easy_setopt(curl, CURLOPT_URL, pURL); + /* file handle: passed to the default callback, it will fread() it */ + curl_easy_setopt(curl, CURLOPT_READDATA, f); + /* get file size */ + curl_easy_setopt(curl, CURLOPT_INFILESIZE, content_length); + /*everything is done here; result 0 means success*/ + int result = curl_easy_perform(curl); + /* goodbye */ + curl_easy_cleanup(curl); + return result; +} + +static void +send_string(const char *pURL, + const char *pContent, + int retryCount, + int retryDelaySeconds) +{ + if (pURL[0] == '\0') + { + error_msg(_("send_string: URL not specified")); return; } - if (pRelease.find("Fedora") != string::npos) + + do { - pProduct = "Fedora"; + int content_length = strlen(pContent); + FILE* f = fmemopen((void*)pContent, content_length, "r"); + if (!f) + { + throw CABRTException(EXCEP_PLUGIN, "send_string: could not open string stream"); + } + int result = put_stream(pURL, f, content_length); + fclose(f); + if (!result) + return; + update_client(_("Sending failed, try it again: %s"), curl_easy_strerror((CURLcode)result)); } - else if (pRelease.find("Red Hat Enterprise Linux") != string::npos) + /*retry the upload if not succesful, wait a bit before next try*/ + while (--retryCount != 0 && (sleep(retryDelaySeconds), 1)); + + throw CABRTException(EXCEP_PLUGIN, "send_string: could not send string"); +} + +static void +send_file(const char *pURL, + const char *pFilename, + int retryCount, + int retryDelaySeconds) +{ + if (pURL[0] == '\0') { - pProduct = "Red Hat Enterprise Linux "; + error_msg(_("send_file: URL not specified")); + return; } - string::size_type pos = pRelease.find("release"); - pos = pRelease.find(" ", pos) + 1; - while (pRelease[pos] != ' ') + + update_client(_("Sending file %s to %s"), pFilename, pURL); + + do { - pVersion += pRelease[pos]; - if (pProduct == "Red Hat Enterprise Linux ") + FILE* f = fopen(pFilename, "r"); + if (!f) { - pProduct += pRelease[pos]; + throw CABRTException(EXCEP_PLUGIN, "send_file: could not open string stream"); } - pos++; + struct stat buf; + fstat(fileno(f), &buf); /* can't fail */ + int content_length = buf.st_size; + int result = put_stream(pURL, f, content_length); + fclose(f); + if (!result) + return; + update_client(_("Sending failed, try it again: %s"), curl_easy_strerror((CURLcode)result)); } + /*retry the upload if not succesful, wait a bit before next try*/ + while (--retryCount != 0 && (sleep(retryDelaySeconds), 1)); + + throw CABRTException(EXCEP_PLUGIN, "send_file: could not send file"); +} + +static string +resolve_relative_url(const char *url, const char *base) +{ + // if 'url' is relative (not absolute) combine it with 'base' + // (which must be absolute) + // Only works in limited cases: + // 0) url is already absolute + // 1) url starts with two slashes + // 2) url starts with one slash + + const char *colon = strchr(url, ':'); + const char *slash = strchr(url, '/'); + + if (colon && (!slash || colon < slash)) + { + return url; + } + + const char *end_of_protocol = strchr(base, ':'); + string protocol(base, end_of_protocol - base); + + end_of_protocol += 3; /* skip "://" */ + const char *end_of_host = strchr(end_of_protocol, '/'); + string host(end_of_protocol, end_of_host - end_of_protocol); + + if (url[0] == '/') + { + if (url[1] == '/') + { + protocol += ':'; + protocol += url; + return protocol; + } + protocol += "://"; + protocol += host; + protocol += url; + return protocol; + } + throw CABRTException(EXCEP_PLUGIN, "resolve_relative_url: unhandled relative url"); +} + +// +// struct_find_XXXX +// abstract all the busy work of getting a field's value from +// a struct. XXXX is a type. +// Return true/false = the field is in the struct +// If true, return the field's value in 'value'. +// +// This function currently just assumes that the value in the +// field can be read into the type of 'value'. This should probably +// be fixed to either convert the fields value to the type of 'value' +// or error specifically/usefully. +// +// This function probably should be converted to an overloaded function +// (overloaded on the type of 'value'). It could also be a function +// template. +// + +static bool +struct_find_int(xmlrpc_env* env, xmlrpc_value* result, + const char* fieldName, int& value) +{ + xmlrpc_value* an_xmlrpc_value; + xmlrpc_struct_find_value(env, result, fieldName, &an_xmlrpc_value); + throw_if_xml_fault_occurred(env); + if (an_xmlrpc_value) + { + xmlrpc_read_int(env, an_xmlrpc_value, &value); + throw_if_xml_fault_occurred(env); + xmlrpc_DECREF(an_xmlrpc_value); + return true; + } + return false; +} + +static bool +struct_find_string(xmlrpc_env* env, xmlrpc_value* result, + const char* fieldName, string& value) +{ + xmlrpc_value* an_xmlrpc_value; + xmlrpc_struct_find_value(env, result, fieldName, &an_xmlrpc_value); + throw_if_xml_fault_occurred(env); + if (an_xmlrpc_value) + { + const char* value_s; + xmlrpc_read_string(env, an_xmlrpc_value, &value_s); + throw_if_xml_fault_occurred(env); + value = value_s; + xmlrpc_DECREF(an_xmlrpc_value); + free((void*)value_s); + return true; + } + return false; +} + + +/* + * Static namespace for xmlrpc stuff. + * Used mainly to ensure we always destroy xmlrpc client and server_info. + */ + +namespace { + +struct ctx: public abrt_xmlrpc_conn { + ctx(const char* url, bool no_ssl_verify): abrt_xmlrpc_conn(url, no_ssl_verify) {} + + string login(const char* login, const char* passwd); + string new_bug(const char *auth_cookie, const map_crash_report_t& pCrashReport); + string request_upload(const char* auth_cookie, const char* pTicketName, + const char* fileName, const char* description); + void add_attachments(const char* xmlrpc_URL, + const char* auth_cookie, + const char* pTicketName, + const map_crash_report_t& pCrashReport, + int retryCount, + int retryDelaySeconds); +}; + +string +ctx::login(const char* login, const char* passwd) +{ + xmlrpc_env env; + xmlrpc_env_init(&env); + + xmlrpc_value* param = xmlrpc_build_value(&env, "(ss)", login, passwd); + throw_if_xml_fault_occurred(&env); + + xmlrpc_value* result; + xmlrpc_client_call2(&env, m_pClient, m_pServer_info, "Catcut.auth", param, &result); + xmlrpc_DECREF(param); + throw_if_xml_fault_occurred(&env); + + xmlrpc_value *cookie_xml; + const char *cookie; + string cookie_str; + xmlrpc_struct_find_value(&env, result, "cookie", &cookie_xml); + throw_if_xml_fault_occurred(&env); + xmlrpc_read_string(&env, cookie_xml, &cookie); + throw_if_xml_fault_occurred(&env); + cookie_str = cookie; + /* xmlrpc_read_string returns *malloc'ed ptr*. + * doc is not very clear on it, but I looked in xmlrpc sources. */ + free((void*)cookie); + xmlrpc_DECREF(cookie_xml); + + xmlrpc_DECREF(result); + + return cookie_str; } -static string new_bug(const char *auth_cookie, const map_crash_report_t& pCrashReport) +string +ctx::new_bug(const char *auth_cookie, const map_crash_report_t& pCrashReport) { + xmlrpc_env env; + xmlrpc_env_init(&env); + string package = pCrashReport.find(FILENAME_PACKAGE)->second[CD_CONTENT]; string component = pCrashReport.find(FILENAME_COMPONENT)->second[CD_CONTENT]; string release = pCrashReport.find(FILENAME_RELEASE)->second[CD_CONTENT]; @@ -216,7 +329,7 @@ static string new_bug(const char *auth_cookie, const map_crash_report_t& pCrashR string product; string version; - get_product_and_version(release, product, version); + parse_release(release.c_str(), product, version); xmlrpc_value *param = xmlrpc_build_value(&env, "(s{s:s,s:s,s:s,s:s,s:s,s:s,s:s})", auth_cookie, @@ -228,23 +341,23 @@ static string new_bug(const char *auth_cookie, const map_crash_report_t& pCrashR "status_whiteboard", status_whiteboard.c_str(), "platform", arch.c_str() ); - throw_if_xml_fault_occurred(); + throw_if_xml_fault_occurred(&env); xmlrpc_value *result; - xmlrpc_client_call2(&env, client, server_info, "Catcut.createTicket", param, &result); - throw_if_xml_fault_occurred(); + xmlrpc_client_call2(&env, m_pClient, m_pServer_info, "Catcut.createTicket", param, &result); xmlrpc_DECREF(param); + throw_if_xml_fault_occurred(&env); xmlrpc_value *bug_id_xml; const char *bug_id; string bug_id_str; xmlrpc_struct_find_value(&env, result, "ticket", &bug_id_xml); - throw_if_xml_fault_occurred(); + throw_if_xml_fault_occurred(&env); xmlrpc_read_string(&env, bug_id_xml, &bug_id); - throw_if_xml_fault_occurred(); + throw_if_xml_fault_occurred(&env); bug_id_str = bug_id; log("New bug id: %s", bug_id); - update_client(_("New bug id: ") + bug_id_str); + update_client(_("New bug id: %s"), bug_id); free((void*)bug_id); xmlrpc_DECREF(bug_id_xml); @@ -253,81 +366,177 @@ static string new_bug(const char *auth_cookie, const map_crash_report_t& pCrashR return bug_id_str; } -//static -//void add_attachments(const string& pBugId, const map_crash_report_t& pCrashReport) -//{ -// xmlrpc_value* result = NULL; -// -// map_crash_report_t::const_iterator it = pCrashReport.begin(); -// for (; it != pCrashReport.end(); it++) -// { -// if (it->second[CD_TYPE] == CD_ATT) -// { -// string description = "File: " + it->first; -// const string& to_encode = it->second[CD_CONTENT]; -// char *encoded64 = encode_base64(to_encode.c_str(), to_encode.length()); -// xmlrpc_value* param = xmlrpc_build_value(&env,"(s{s:s,s:s,s:s,s:s})", -// pBugId.c_str(), -// "description", description.c_str(), -// "filename", it->first.c_str(), -// "contenttype", "text/plain", -// "data", encoded64 -// ); -// free(encoded64); -// throw_if_xml_fault_occurred(); -// -//// catcut has this API: -//// struct response requestUpload(string cookie, string ticket, string filename, string description) -////response MUST include "errno", "errmsg" members; if an upload is approved, -////a "URL" MUST be returned in the response. The description string -////should include a brief description of the file. -//// -////The client should upload the file via HTTP PUT to the provided -////URL. The provided URL may be absolute or relative, if relative it must -////be combined with the base URL of the XML-RPC server using the usual -////rules for relative URL's (RFC 3986). -// xmlrpc_client_call2(&env, client, server_info, "catcut.addAttachment", param, &result); -// throw_if_xml_fault_occurred(); -// } -// } -//} +string +ctx::request_upload(const char* auth_cookie, const char* pTicketName, + const char* fileName, const char* description) +{ + xmlrpc_env env; + xmlrpc_env_init(&env); + + xmlrpc_value* param = xmlrpc_build_value(&env, "(ssss)", + auth_cookie, + pTicketName, + fileName, + description); + throw_if_xml_fault_occurred(&env); + + xmlrpc_value* result = NULL; + xmlrpc_client_call2(&env, m_pClient, m_pServer_info, "Catcut.requestUpload", param, &result); + xmlrpc_DECREF(param); + throw_if_xml_fault_occurred(&env); + + string URL; + bool has_URL = struct_find_string(&env, result, "uri", URL); + if (!has_URL || URL == "") + { + int err; + bool has_errno = struct_find_int(&env, result, "errno", err); + if (has_errno && err) + { + string errmsg; + bool has_errmsg = struct_find_string(&env, result, "errmsg", errmsg); + if (has_errmsg) + { + log("error returned by requestUpload: %s", errmsg.c_str()); + update_client(_("error returned by requestUpload: %s"), errmsg.c_str()); + } + else + { + log("error returned by requestUpload: %d", err); + update_client(_("error returned by requestUpload: %d"), err); + } + } + else + { + log("no URL returned by requestUpload, and no err"); + update_client(_("no URL returned by requestUpload, and no errno")); + } + } + + log("requestUpload returned URL: %s", URL.c_str()); + update_client(_("requestUpload returned URL: %s"), URL.c_str()); + + xmlrpc_DECREF(result); + return URL; +} + +void +ctx::add_attachments(const char* xmlrpc_URL, + const char* auth_cookie, + const char* pTicketName, + const map_crash_report_t& pCrashReport, + int retryCount, + int retryDelaySeconds) +{ + + map_crash_report_t::const_iterator it = pCrashReport.begin(); + for (; it != pCrashReport.end(); it++) + { + if (it->second[CD_TYPE] == CD_ATT) + { + update_client(_("Attaching (CD_ATT): %s"), it->first.c_str()); + + string description = "File: " + it->first; + string URL = request_upload(auth_cookie, + pTicketName, + it->first.c_str(), + description.c_str()); + + URL = resolve_relative_url(URL.c_str(), xmlrpc_URL); + + log("rebased URL: %s", URL.c_str()); + update_client(_("rebased URL: %s"), URL.c_str()); + + send_string(URL.c_str(), it->second[CD_CONTENT].c_str(), + retryCount, retryDelaySeconds); + } + else if (it->second[CD_TYPE] == CD_BIN) + { + update_client(_("Attaching (CD_ATT): %s"), it->first.c_str()); + + string description = "File: " + it->first; + string URL = request_upload(auth_cookie, + pTicketName, + it->first.c_str(), + description.c_str()); + + URL = resolve_relative_url(URL.c_str(), xmlrpc_URL); + + log("rebased URL: %s", URL.c_str()); + update_client(_("rebased URL: %s"), URL.c_str()); + + send_file(URL.c_str(), it->second[CD_CONTENT].c_str(), + retryCount, retryDelaySeconds); + } + } +} + +} /* namespace */ + + +/* + * CReporterCatcut + */ CReporterCatcut::CReporterCatcut() : m_sCatcutURL("http://127.0.0.1:8080/catcut/xmlrpc"), - m_bNoSSLVerify(false) + m_bNoSSLVerify(false), + m_nRetryCount(3), + m_nRetryDelay(20) {} CReporterCatcut::~CReporterCatcut() {} string CReporterCatcut::Report(const map_crash_report_t& pCrashReport, - const map_plugin_settings_t& pSettings, const string& pArgs) + const map_plugin_settings_t& pSettings, + const string& pArgs) { update_client(_("Creating new bug...")); try { - new_xmlrpc_client(m_sCatcutURL.c_str(), m_bNoSSLVerify); - string auth_cookie = login(m_sLogin.c_str(), m_sPassword.c_str()); - string bug_id = (auth_cookie != "") ? new_bug(auth_cookie.c_str(), pCrashReport) : ""; -// add_attachments(to_string(bug_id), pCrashReport); -// update_client(_("Logging out...")); -// logout(); - destroy_xmlrpc_client(); - return "New catcut bug ID: " + bug_id; + ctx catcut_server(m_sCatcutURL.c_str(), m_bNoSSLVerify); + string auth_cookie = catcut_server.login(m_sLogin.c_str(), m_sPassword.c_str()); + string message; + if (auth_cookie != "") + { + string ticket_name = catcut_server.new_bug(auth_cookie.c_str(), pCrashReport); + if (ticket_name != "") + { + catcut_server.add_attachments( + m_sCatcutURL.c_str(), + auth_cookie.c_str(), + ticket_name.c_str(), + pCrashReport, + m_nRetryCount, + m_nRetryDelay + ); + message = "New catcut bug ID: " + ticket_name; + } + else + { + message = "Error could not create ticket"; + } + } + else + { + message = "Error could not create ticket"; + } + return message; } catch (CABRTException& e) { - destroy_xmlrpc_client(); - throw CABRTException(EXCEP_PLUGIN, string("CReporterCatcut::Report(): ") + e.what()); + throw CABRTException(EXCEP_PLUGIN, e.what()); } } void CReporterCatcut::SetSettings(const map_plugin_settings_t& pSettings) { - map_plugin_settings_t::const_iterator it; - map_plugin_settings_t::const_iterator end = pSettings.end(); + m_pSettings = pSettings; + map_plugin_settings_t::const_iterator end = pSettings.end(); + map_plugin_settings_t::const_iterator it; it = pSettings.find("CatcutURL"); if (it != end) { @@ -346,20 +555,18 @@ void CReporterCatcut::SetSettings(const map_plugin_settings_t& pSettings) it = pSettings.find("NoSSLVerify"); if (it != end) { - m_bNoSSLVerify = it->second == "yes"; + m_bNoSSLVerify = string_to_bool(it->second.c_str()); + } + it = pSettings.find("RetryCount"); + if (it != end) + { + m_nRetryCount = atoi(it->second.c_str()); + } + it = pSettings.find("RetryDelay"); + if (it != end) + { + m_nRetryDelay = atoi(it->second.c_str()); } -} - -map_plugin_settings_t CReporterCatcut::GetSettings() -{ - map_plugin_settings_t ret; - - ret["CatcutURL"] = m_sCatcutURL; - ret["Login"] = m_sLogin; - ret["Password"] = m_sPassword; - ret["NoSSLVerify"] = m_bNoSSLVerify ? "yes" : "no"; - - return ret; } PLUGIN_INFO(REPORTER, |