From 83a6ce9ad4b1828e163dc7172ef603201b748473 Mon Sep 17 00:00:00 2001 From: Nikola Pajkovsky Date: Tue, 10 Aug 2010 10:21:25 +0200 Subject: lower case direcotry(no code changed) Signed-off-by: Nikola Pajkovsky --- lib/plugins/Bugzilla.conf | 10 + lib/plugins/Bugzilla.cpp | 982 ++++++++++++++++++++++++++++++ lib/plugins/Bugzilla.glade | 246 ++++++++ lib/plugins/Bugzilla.h | 47 ++ lib/plugins/CCpp.conf | 42 ++ lib/plugins/CCpp.cpp | 1064 +++++++++++++++++++++++++++++++++ lib/plugins/CCpp.h | 53 ++ lib/plugins/CCpp_sha1.cpp | 214 +++++++ lib/plugins/CCpp_sha1.h | 32 + lib/plugins/FileTransfer.conf | 35 ++ lib/plugins/FileTransfer.cpp | 362 +++++++++++ lib/plugins/FileTransfer.h | 48 ++ lib/plugins/Kerneloops.conf | 19 + lib/plugins/Kerneloops.cpp | 145 +++++ lib/plugins/Kerneloops.h | 42 ++ lib/plugins/KerneloopsReporter.cpp | 145 +++++ lib/plugins/KerneloopsReporter.glade | 118 ++++ lib/plugins/KerneloopsReporter.h | 51 ++ lib/plugins/KerneloopsScanner.cpp | 203 +++++++ lib/plugins/KerneloopsScanner.h | 42 ++ lib/plugins/KerneloopsSysLog.cpp | 366 ++++++++++++ lib/plugins/KerneloopsSysLog.h | 34 ++ lib/plugins/Logger.conf | 6 + lib/plugins/Logger.cpp | 90 +++ lib/plugins/Logger.glade | 135 +++++ lib/plugins/Logger.h | 44 ++ lib/plugins/Mailx.conf | 15 + lib/plugins/Mailx.cpp | 195 ++++++ lib/plugins/Mailx.glade | 184 ++++++ lib/plugins/Mailx.h | 48 ++ lib/plugins/Makefile.am | 151 +++++ lib/plugins/Python.conf | 1 + lib/plugins/Python.cpp | 100 ++++ lib/plugins/Python.h | 36 ++ lib/plugins/Python_hash.cpp | 445 ++++++++++++++ lib/plugins/Python_hash.h | 29 + lib/plugins/RHTSupport.conf | 9 + lib/plugins/RHTSupport.cpp | 312 ++++++++++ lib/plugins/RHTSupport.glade | 213 +++++++ lib/plugins/RHTSupport.h | 45 ++ lib/plugins/ReportUploader.conf | 21 + lib/plugins/ReportUploader.cpp | 505 ++++++++++++++++ lib/plugins/ReportUploader.glade | 249 ++++++++ lib/plugins/ReportUploader.h | 55 ++ lib/plugins/RunApp.cpp | 76 +++ lib/plugins/RunApp.h | 34 ++ lib/plugins/SOSreport.conf | 1 + lib/plugins/SOSreport.cpp | 162 +++++ lib/plugins/SOSreport.h | 31 + lib/plugins/SQLite3.conf | 4 + lib/plugins/SQLite3.cpp | 681 +++++++++++++++++++++ lib/plugins/SQLite3.h | 56 ++ lib/plugins/abrt-Bugzilla.7 | 43 ++ lib/plugins/abrt-FileTransfer.7 | 72 +++ lib/plugins/abrt-KerneloopsReporter.7 | 40 ++ lib/plugins/abrt-KerneloopsScanner.7 | 46 ++ lib/plugins/abrt-Logger.7 | 44 ++ lib/plugins/abrt-Mailx.7 | 57 ++ lib/plugins/abrt-ReportUploader.7 | 55 ++ lib/plugins/abrt-RunApp.7 | 43 ++ lib/plugins/abrt-SQLite3.7 | 36 ++ lib/plugins/abrt-plugins.7 | 44 ++ 62 files changed, 8713 insertions(+) create mode 100644 lib/plugins/Bugzilla.conf create mode 100644 lib/plugins/Bugzilla.cpp create mode 100644 lib/plugins/Bugzilla.glade create mode 100644 lib/plugins/Bugzilla.h create mode 100644 lib/plugins/CCpp.conf create mode 100644 lib/plugins/CCpp.cpp create mode 100644 lib/plugins/CCpp.h create mode 100644 lib/plugins/CCpp_sha1.cpp create mode 100644 lib/plugins/CCpp_sha1.h create mode 100644 lib/plugins/FileTransfer.conf create mode 100644 lib/plugins/FileTransfer.cpp create mode 100644 lib/plugins/FileTransfer.h create mode 100644 lib/plugins/Kerneloops.conf create mode 100644 lib/plugins/Kerneloops.cpp create mode 100644 lib/plugins/Kerneloops.h create mode 100644 lib/plugins/KerneloopsReporter.cpp create mode 100644 lib/plugins/KerneloopsReporter.glade create mode 100644 lib/plugins/KerneloopsReporter.h create mode 100644 lib/plugins/KerneloopsScanner.cpp create mode 100644 lib/plugins/KerneloopsScanner.h create mode 100644 lib/plugins/KerneloopsSysLog.cpp create mode 100644 lib/plugins/KerneloopsSysLog.h create mode 100644 lib/plugins/Logger.conf create mode 100644 lib/plugins/Logger.cpp create mode 100644 lib/plugins/Logger.glade create mode 100644 lib/plugins/Logger.h create mode 100644 lib/plugins/Mailx.conf create mode 100644 lib/plugins/Mailx.cpp create mode 100644 lib/plugins/Mailx.glade create mode 100644 lib/plugins/Mailx.h create mode 100644 lib/plugins/Makefile.am create mode 100644 lib/plugins/Python.conf create mode 100644 lib/plugins/Python.cpp create mode 100644 lib/plugins/Python.h create mode 100644 lib/plugins/Python_hash.cpp create mode 100644 lib/plugins/Python_hash.h create mode 100644 lib/plugins/RHTSupport.conf create mode 100644 lib/plugins/RHTSupport.cpp create mode 100644 lib/plugins/RHTSupport.glade create mode 100644 lib/plugins/RHTSupport.h create mode 100644 lib/plugins/ReportUploader.conf create mode 100644 lib/plugins/ReportUploader.cpp create mode 100644 lib/plugins/ReportUploader.glade create mode 100644 lib/plugins/ReportUploader.h create mode 100644 lib/plugins/RunApp.cpp create mode 100644 lib/plugins/RunApp.h create mode 100644 lib/plugins/SOSreport.conf create mode 100644 lib/plugins/SOSreport.cpp create mode 100644 lib/plugins/SOSreport.h create mode 100644 lib/plugins/SQLite3.conf create mode 100644 lib/plugins/SQLite3.cpp create mode 100644 lib/plugins/SQLite3.h create mode 100644 lib/plugins/abrt-Bugzilla.7 create mode 100644 lib/plugins/abrt-FileTransfer.7 create mode 100644 lib/plugins/abrt-KerneloopsReporter.7 create mode 100644 lib/plugins/abrt-KerneloopsScanner.7 create mode 100644 lib/plugins/abrt-Logger.7 create mode 100644 lib/plugins/abrt-Mailx.7 create mode 100644 lib/plugins/abrt-ReportUploader.7 create mode 100644 lib/plugins/abrt-RunApp.7 create mode 100644 lib/plugins/abrt-SQLite3.7 create mode 100644 lib/plugins/abrt-plugins.7 (limited to 'lib/plugins') diff --git a/lib/plugins/Bugzilla.conf b/lib/plugins/Bugzilla.conf new file mode 100644 index 00000000..4eebd994 --- /dev/null +++ b/lib/plugins/Bugzilla.conf @@ -0,0 +1,10 @@ +Enabled = yes + +# Bugzilla URL +BugzillaURL = https://bugzilla.redhat.com/ +# yes means that ssl certificates will be checked +SSLVerify = yes +# your login has to exist, if you don have any, please create one +Login = +# your password +Password = diff --git a/lib/plugins/Bugzilla.cpp b/lib/plugins/Bugzilla.cpp new file mode 100644 index 00000000..f3967cdf --- /dev/null +++ b/lib/plugins/Bugzilla.cpp @@ -0,0 +1,982 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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 "abrtlib.h" +#include "abrt_xmlrpc.h" +#include "Bugzilla.h" +#include "crash_types.h" +#include "debug_dump.h" +#include "abrt_exception.h" +#include "comm_layer_inner.h" +#ifdef HAVE_CONFIG_H +# include +#endif + +#define XML_RPC_SUFFIX "/xmlrpc.cgi" +#define MAX_HOPS 5 + +/* + * TODO: npajkovs: better deallocation of xmlrpc value + * npajkovs: better gathering function which collects all information from bugzilla + * npajkovs: figure out how to deal with cloning bugs + * npajkovs: check if attachment was uploaded successul an if not try it again(max 3 times) + * and if it still fails. retrun successful, but mention that attaching failed + * npajkovs: add option to set comment privat + */ + +struct bug_info { + const char* bug_status; + const char* bug_resolution; + const char* bug_reporter; + const char* bug_product; + xmlrpc_int32 bug_dup_id; + std::vector bug_cc; +}; + +static void bug_info_init(struct bug_info* bz) +{ + bz->bug_status = NULL; + bz->bug_resolution = NULL; + bz->bug_reporter = NULL; + bz->bug_product = NULL; + bz->bug_dup_id = -1; +} + +static void bug_info_destroy(struct bug_info* bz) +{ + free((void*)bz->bug_status); + free((void*)bz->bug_resolution); + free((void*)bz->bug_reporter); + free((void*)bz->bug_product); + + if (!bz->bug_cc.empty()) + { + for (int ii = 0; ii < bz->bug_cc.size(); ii++) + free((void*)bz->bug_cc[ii]); + + bz->bug_cc.clear(); + } +} + +static int am_i_in_cc(const struct bug_info* bz, const char* login) +{ + if (bz->bug_cc.empty()) + return -1; + + int size = bz->bug_cc.size(); + for (int ii = 0; ii < size; ii++) + { + if (strcmp(login, bz->bug_cc[ii]) == 0) + return 0; + } + return -1; +} + +/* + * Static namespace for xmlrpc stuff. + * Used mainly to ensure we always destroy xmlrpc client and server_info. + */ + +namespace { + +struct ctx: public abrt_xmlrpc_conn { + xmlrpc_env env; + + ctx(const char* url, bool ssl_verify): abrt_xmlrpc_conn(url, ssl_verify) + { xmlrpc_env_init(&env); } + ~ctx() { xmlrpc_env_clean(&env); } + + void login(const char* login, const char* passwd); + void logout(); + + const char* get_bug_status(xmlrpc_value* result_xml); + const char* get_bug_resolution(xmlrpc_value* result_xml); + const char* get_bug_reporter(xmlrpc_value* result_xml); + const char* get_bug_product(xmlrpc_value* relult_xml); + + xmlrpc_value* call_quicksearch_duphash(const char* component, const char* release, const char* duphash); + xmlrpc_value* get_cc_member(xmlrpc_value* result_xml); + xmlrpc_value* get_member(const char* member, xmlrpc_value* result_xml); + + int get_array_size(xmlrpc_value* result_xml); + xmlrpc_int32 get_bug_id(xmlrpc_value* result_xml); + xmlrpc_int32 get_bug_dup_id(xmlrpc_value* result_xml); + void get_bug_cc(xmlrpc_value* result_xml, struct bug_info* bz); + int add_plus_one_cc(xmlrpc_int32 bug_id, const char* login); + xmlrpc_int32 new_bug(const map_crash_data_t& pCrashData, int depend_on_bugno); + int add_attachments(const char* bug_id_str, const map_crash_data_t& pCrashData); + int get_bug_info(struct bug_info* bz, xmlrpc_int32 bug_id); + int add_comment(xmlrpc_int32 bug_id, const char* comment, bool is_private); + + xmlrpc_value* call(const char* method, const char* format, ...); +}; + +xmlrpc_value* ctx::call(const char* method, const char* format, ...) +{ + va_list args; + xmlrpc_value* param = NULL; + xmlrpc_value* result = NULL; + const char* suffix; + + va_start(args, format); + xmlrpc_build_value_va(&env, format, args, ¶m, &suffix); + va_end(args); + + if (!env.fault_occurred) + { + if (*suffix != '\0') + { + xmlrpc_env_set_fault_formatted( + &env, XMLRPC_INTERNAL_ERROR, "Junk after the argument " + "specifier: '%s'. There must be exactly one arument.", + suffix); + + xmlrpc_DECREF(param); + return NULL; + } + + xmlrpc_client_call2(&env, m_pClient, m_pServer_info, method, param, &result); + xmlrpc_DECREF(param); + if (env.fault_occurred) + return NULL; + } + + + return result; +} + +xmlrpc_value* ctx::get_member(const char* member, xmlrpc_value* result_xml) +{ + xmlrpc_value* cc_member = NULL; + xmlrpc_struct_find_value(&env, result_xml, member, &cc_member); + if (env.fault_occurred) + return NULL; + + return cc_member; +} + +int ctx::get_array_size(xmlrpc_value* result_xml) +{ + int size = xmlrpc_array_size(&env, result_xml); + if (env.fault_occurred) + return -1; + + return size; +} + +xmlrpc_int32 ctx::get_bug_dup_id(xmlrpc_value* result_xml) +{ + xmlrpc_value* dup_id = get_member("dup_id", result_xml); + if (!dup_id) + return -1; + + xmlrpc_int32 dup_id_int = -1; + xmlrpc_read_int(&env, dup_id, &dup_id_int); + xmlrpc_DECREF(dup_id); + if (env.fault_occurred) + return -1; + + VERB3 log("got dup_id: %i", dup_id_int); + return dup_id_int; +} + +const char* ctx::get_bug_product(xmlrpc_value* result_xml) +{ + xmlrpc_value* product_member = get_member("product", result_xml); + if (!product_member) //should never happend. Each bug has to set up product + return NULL; + + const char* product = NULL; + xmlrpc_read_string(&env, product_member, &product); + xmlrpc_DECREF(product_member); + if (env.fault_occurred) + return NULL; + + if (*product != '\0') + { + VERB3 log("got bug product: %s", product); + return product; + } + + free((void*)product); + return NULL; +} + +const char* ctx::get_bug_reporter(xmlrpc_value* result_xml) +{ + xmlrpc_value* reporter_member = get_member("reporter", result_xml); + if (!reporter_member) + return NULL; + + const char* reporter = NULL; + xmlrpc_read_string(&env, reporter_member, &reporter); + xmlrpc_DECREF(reporter_member); + if (env.fault_occurred) + return NULL; + + if (*reporter != '\0') + { + VERB3 log("got bug reporter: %s", reporter); + return reporter; + } + free((void*)reporter); + return NULL; +} + +const char* ctx::get_bug_resolution(xmlrpc_value* result_xml) +{ + xmlrpc_value* bug_resolution = get_member("resolution", result_xml); + if (!bug_resolution) + return NULL; + + const char* resolution_str = NULL; + xmlrpc_read_string(&env, bug_resolution, &resolution_str); + xmlrpc_DECREF(bug_resolution); + if (env.fault_occurred) + return NULL; + + if (*resolution_str != '\0') + { + VERB3 log("got resolution: %s", resolution_str); + return resolution_str; + } + free((void*)resolution_str); + return NULL; +} + +const char* ctx::get_bug_status(xmlrpc_value* result_xml) +{ + xmlrpc_value* bug_status = get_member("bug_status", result_xml); + if (!bug_status) + return NULL; + + const char* status_str = NULL; + xmlrpc_read_string(&env, bug_status, &status_str); + xmlrpc_DECREF(bug_status); + if (env.fault_occurred) + return NULL; + + if (*status_str != '\0') + { + VERB3 log("got bug_status: %s", status_str); + return status_str; + } + free((void*)status_str); + return NULL; +} + +void ctx::get_bug_cc(xmlrpc_value* result_xml, struct bug_info* bz) +{ + xmlrpc_value* cc_member = get_member("cc", result_xml); + if (!cc_member) + return; + + int array_size = xmlrpc_array_size(&env, cc_member); + if (array_size == -1) + return; + + VERB3 log("count members on cc %i", array_size); + + for (int i = 0; i < array_size; i++) + { + xmlrpc_value* item = NULL; + xmlrpc_array_read_item(&env, cc_member, i, &item); + if (env.fault_occurred) + return; + + if (item) + { + const char* cc = NULL; + xmlrpc_read_string(&env, item, &cc); + xmlrpc_DECREF(item); + if (env.fault_occurred) + { + xmlrpc_DECREF(cc_member); + return; + } + + if (*cc != '\0') + { + bz->bug_cc.push_back(cc); + VERB3 log("member on cc is %s", cc); + continue; + } + free((void*)cc); + } + } + xmlrpc_DECREF(cc_member); + return; +} + +xmlrpc_value* ctx::call_quicksearch_duphash(const char* component, const char* release, const char* duphash) +{ + char *query = NULL; + if (!release) + query = xasprintf("ALL component:\"%s\" whiteboard:\"%s\"", component, duphash); + else + { + char *product = NULL; + char *version = NULL; + parse_release(release, &product, &version); + query = xasprintf("ALL component:\"%s\" whiteboard:\"%s\" product:\"%s\"", + component, duphash, product + ); + free(product); + free(version); + } + + VERB3 log("quicksearch for `%s'", query); + xmlrpc_value *ret = call("Bug.search", "({s:s})", "quicksearch", query); + free(query); + return ret; +} + +xmlrpc_int32 ctx::get_bug_id(xmlrpc_value* result_xml) +{ + xmlrpc_value* item = NULL; + xmlrpc_array_read_item(&env, result_xml, 0, &item); + if (env.fault_occurred) + return -1; + + xmlrpc_value* bug = get_member("bug_id", item); + xmlrpc_DECREF(item); + if (!bug) + return -1; + + xmlrpc_int32 bug_id = -1; + xmlrpc_read_int(&env, bug, &bug_id); + xmlrpc_DECREF(bug); + if (env.fault_occurred) + return -1; + + VERB3 log("got bug_id %d", (int)bug_id); + return bug_id; +} + +int ctx::add_plus_one_cc(xmlrpc_int32 bug_id, const char* login) +{ + xmlrpc_value* result = call("Bug.update", "({s:i,s:{s:(s)}})", "ids", (int)bug_id, "updates", "add_cc", login); + if (result) + xmlrpc_DECREF(result); + return result ? 0 : -1; +} + +int ctx::add_comment(xmlrpc_int32 bug_id, const char* comment, bool is_private) +{ + xmlrpc_value* result = call("Bug.add_comment", "({s:i,s:s,s:b})", "id", (int)bug_id, + "comment", comment, + "private", is_private); + if (result) + xmlrpc_DECREF(result); + return result ? 0 : -1; +} + +xmlrpc_int32 ctx::new_bug(const map_crash_data_t& pCrashData, int depend_on_bugno) +{ + const std::string& package = get_crash_data_item_content(pCrashData, FILENAME_PACKAGE); + const std::string& component = get_crash_data_item_content(pCrashData, FILENAME_COMPONENT); + const std::string& release = get_crash_data_item_content(pCrashData, FILENAME_RELEASE); + const std::string& arch = get_crash_data_item_content(pCrashData, FILENAME_ARCHITECTURE); + const std::string& duphash = get_crash_data_item_content(pCrashData, CD_DUPHASH); + const char *reason = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_REASON); + const char *function = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_CRASH_FUNCTION); + + std::string summary = "[abrt] " + package; + if (function != NULL && strlen(function) < 30) + { + summary += ": "; + summary += function; + } + + if (reason != NULL) + { + summary += ": "; + summary += reason; + } + std::string status_whiteboard = "abrt_hash:" + duphash; + + std::string description = "abrt version: "VERSION"\n"; + description += make_description_bz(pCrashData); + + char *product = NULL; + char *version = NULL; + parse_release(release.c_str(), &product, &version); + + xmlrpc_value* result = NULL; + if (depend_on_bugno > -1) + { + result = call("Bug.create", "({s:s,s:s,s:s,s:s,s:s,s:s,s:s,s:i})", + "product", product, + "component", component.c_str(), + "version", version, + "summary", summary.c_str(), + "description", description.c_str(), + "status_whiteboard", status_whiteboard.c_str(), + "platform", arch.c_str(), + "dependson", depend_on_bugno + ); + } + else + { + result = call("Bug.create", "({s:s,s:s,s:s,s:s,s:s,s:s,s:s})", + "product", product, + "component", component.c_str(), + "version", version, + "summary", summary.c_str(), + "description", description.c_str(), + "status_whiteboard", status_whiteboard.c_str(), + "platform", arch.c_str() + ); + + } + free(product); + free(version); + + if (!result) + return -1; + + xmlrpc_value* id = get_member("id", result); + xmlrpc_DECREF(result); + if (!id) + return -1; + + xmlrpc_int32 bug_id = -1; + xmlrpc_read_int(&env, id, &bug_id); + xmlrpc_DECREF(id); + if (env.fault_occurred) + return -1; + + log("New bug id: %i", (int)bug_id); + update_client(_("New bug id: %i"), (int)bug_id); + + return bug_id; +} + +int ctx::add_attachments(const char* bug_id_str, const map_crash_data_t& pCrashData) +{ + map_crash_data_t::const_iterator it = pCrashData.begin(); + for (; it != pCrashData.end(); it++) + { + const std::string &itemname = it->first; + const std::string &type = it->second[CD_TYPE]; + const std::string &content = it->second[CD_CONTENT]; + + if (type == CD_TXT + && (content.length() > CD_TEXT_ATT_SIZE || itemname == FILENAME_BACKTRACE) + ) { + char *encoded64 = encode_base64(content.c_str(), content.length()); + xmlrpc_value* result = call("bugzilla.addAttachment", "(s{s:s,s:s,s:s,s:s})", bug_id_str, + "description", ("File: " + itemname).c_str(), + "filename", itemname.c_str(), + "contenttype", "text/plain", + "data", encoded64 + ); + free(encoded64); + if (!result) + return -1; + + xmlrpc_DECREF(result); + } + } + return 0; +} + +int ctx::get_bug_info(struct bug_info* bz, xmlrpc_int32 bug_id) +{ + xmlrpc_value* result = call("bugzilla.getBug", "(s)", to_string(bug_id).c_str()); + if (!result) + return -1; + + if (result) + { + bz->bug_product = get_bug_product(result); + if (bz->bug_product == NULL) + return -1; + + bz->bug_status = get_bug_status(result); + if (bz->bug_status == NULL) + return -1; + + bz->bug_reporter = get_bug_reporter(result); + if (bz->bug_reporter == NULL) + return -1; + + // mandatory when bug status is CLOSED + if (strcmp(bz->bug_status, "CLOSED") == 0) + { + bz->bug_resolution = get_bug_resolution(result); + if ((env.fault_occurred) && (bz->bug_resolution == NULL)) + return -1; + } + + // mandatory when bug status is CLOSED and resolution is DUPLICATE + if ((strcmp(bz->bug_status, "CLOSED") == 0) + && (strcmp(bz->bug_resolution, "DUPLICATE") == 0) + ) { + bz->bug_dup_id = get_bug_dup_id(result); + if (env.fault_occurred) + return -1; + } + + get_bug_cc(result, bz); + if (env.fault_occurred) + return -1; + + xmlrpc_DECREF(result); + return 0; + } + return -1; +} + +//------------------------------------------------------------------- +// ^ +// | nice +// ------------------------------------------------------------------- +// | BAD +// v +//------------------------------------------------------------------- +//TODO: need to rewrite +void ctx::login(const char* login, const char* passwd) +{ + xmlrpc_value* result = call("User.login", "({s:s,s:s})", "login", login, "password", passwd); + + if (!result) + { + std::string errmsg = ssprintf(_("Cannot login. Check Edit->Plugins->Bugzilla and /etc/abrt/plugins/Bugzilla.conf. Server said: %s"), env.fault_string); + error_msg("%s", errmsg.c_str()); // show error in daemon log + throw CABRTException(EXCEP_PLUGIN, "%s", errmsg.c_str()); + } + xmlrpc_DECREF(result); +} + +void ctx::logout() +{ + xmlrpc_value* result = call("User.logout", "(s)", ""); + if (result) + xmlrpc_DECREF(result); + + throw_if_xml_fault_occurred(&env); +} + +} /* namespace */ + + +/* + * CReporterBugzilla + */ + +static map_plugin_settings_t parse_settings(const map_plugin_settings_t& pSettings) +{ + map_plugin_settings_t plugin_settings; + + map_plugin_settings_t::const_iterator end = pSettings.end(); + map_plugin_settings_t::const_iterator it; + it = pSettings.find("BugzillaURL"); + if (it != end) + { + std::string BugzillaURL = it->second; + //remove the /xmlrpc.cgi part from old settings + //FIXME: can be removed after users are informed about new config format + std::string::size_type pos = BugzillaURL.find(XML_RPC_SUFFIX); + if (pos != std::string::npos) + { + BugzillaURL.erase(pos); + } + //remove the trailing '/' + while (BugzillaURL[BugzillaURL.length() - 1] == '/') + { + BugzillaURL.erase(BugzillaURL.length() - 1); + } + plugin_settings["BugzillaXMLRPC"] = BugzillaURL + XML_RPC_SUFFIX; + plugin_settings["BugzillaURL"] = BugzillaURL; + } + + it = pSettings.find("Login"); + if (it == end) + { + /* if any of the option is not set we use the defaults for everything */ + plugin_settings.clear(); + return plugin_settings; + } + plugin_settings["Login"] = it->second; + + it = pSettings.find("Password"); + if (it == end) + { + plugin_settings.clear(); + return plugin_settings; + } + plugin_settings["Password"] = it->second; + + it = pSettings.find("SSLVerify"); + if (it == end) + { + plugin_settings.clear(); + return plugin_settings; + } + plugin_settings["SSLVerify"] = it->second; + + VERB1 log("User settings ok, using them instead of defaults"); + return plugin_settings; +} + +CReporterBugzilla::CReporterBugzilla() : + m_bSSLVerify(true), + m_sBugzillaURL("https://bugzilla.redhat.com"), + m_sBugzillaXMLRPC("https://bugzilla.redhat.com"XML_RPC_SUFFIX), + m_bRatingRequired(true) +{} + +CReporterBugzilla::~CReporterBugzilla() +{} + +std::string CReporterBugzilla::Report(const map_crash_data_t& pCrashData, + const map_plugin_settings_t& pSettings, + const char *pArgs) +{ + xmlrpc_int32 bug_id = -1; + std::string Login; + std::string Password; + std::string BugzillaXMLRPC; + std::string BugzillaURL; + bool SSLVerify; + map_plugin_settings_t settings = parse_settings(pSettings); + /* if parse_settings fails it returns an empty map so we need to use defaults */ + if (!settings.empty()) + { + Login = settings["Login"]; + Password = settings["Password"]; + BugzillaXMLRPC = settings["BugzillaXMLRPC"]; + BugzillaURL = settings["BugzillaURL"]; + SSLVerify = string_to_bool(settings["SSLVerify"].c_str()); + } + else + { + Login = m_sLogin; + Password = m_sPassword; + BugzillaXMLRPC = m_sBugzillaXMLRPC; + BugzillaURL = m_sBugzillaURL; + SSLVerify = m_bSSLVerify; + } + + if ((Login == "") || (Password == "")) + { + VERB3 log("Empty login and password"); + throw CABRTException(EXCEP_PLUGIN, _("Empty login or password.\nPlease check "PLUGINS_CONF_DIR"/Bugzilla.conf.")); + } + + const std::string& component = get_crash_data_item_content(pCrashData, FILENAME_COMPONENT); + const std::string& duphash = get_crash_data_item_content(pCrashData, CD_DUPHASH); + const char *release = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_RELEASE); + + ctx bz_server(BugzillaXMLRPC.c_str(), SSLVerify); + + update_client(_("Logging into bugzilla...")); + bz_server.login(Login.c_str(), Password.c_str()); + + update_client(_("Checking for duplicates...")); + + char *product = NULL; + char *version = NULL; + parse_release(release, &product, &version); + + xmlrpc_value *result; + if (strcmp(product, "Fedora") == 0) + result = bz_server.call_quicksearch_duphash(component.c_str(), product, duphash.c_str()); + else + result = bz_server.call_quicksearch_duphash(component.c_str(), NULL, duphash.c_str()); + + if (!result) + throw_if_xml_fault_occurred(&bz_server.env); + + xmlrpc_value *all_bugs = bz_server.get_member("bugs", result); + xmlrpc_DECREF(result); + + if (!all_bugs) + { + throw_if_xml_fault_occurred(&bz_server.env); + throw CABRTException(EXCEP_PLUGIN, _("Missing mandatory member 'bugs'")); + } + + int all_bugs_size = bz_server.get_array_size(all_bugs); + struct bug_info bz; + int depend_on_bugno = -1; + if (all_bugs_size > 0) + { + bug_id = bz_server.get_bug_id(all_bugs); + xmlrpc_DECREF(all_bugs); + if (bug_id == -1) + throw_if_xml_fault_occurred(&bz_server.env); + + bug_info_init(&bz); + if (bz_server.get_bug_info(&bz, bug_id) == -1) + { + bug_info_destroy(&bz); + throw_if_xml_fault_occurred(&bz_server.env); + throw CABRTException(EXCEP_PLUGIN, _("get_bug_info() failed. Could not collect all mandatory information")); + } + + if (strcmp(bz.bug_product, product) != 0) + { + depend_on_bugno = bug_id; + bug_info_destroy(&bz); + result = bz_server.call_quicksearch_duphash(component.c_str(), release, duphash.c_str()); + if (!result) + throw_if_xml_fault_occurred(&bz_server.env); + + all_bugs = bz_server.get_member("bugs", result); + xmlrpc_DECREF(result); + + if (!all_bugs) + { + throw_if_xml_fault_occurred(&bz_server.env); + throw CABRTException(EXCEP_PLUGIN, _("Missing mandatory member 'bugs'")); + } + + all_bugs_size = bz_server.get_array_size(all_bugs); + if (all_bugs_size > 0) + { + bug_id = bz_server.get_bug_id(all_bugs); + xmlrpc_DECREF(all_bugs); + if (bug_id == -1) + throw_if_xml_fault_occurred(&bz_server.env); + + bug_info_init(&bz); + if (bz_server.get_bug_info(&bz, bug_id) == -1) + { + bug_info_destroy(&bz); + throw_if_xml_fault_occurred(&bz_server.env); + throw CABRTException(EXCEP_PLUGIN, _("get_bug_info() failed. Could not collect all mandatory information")); + } + } + else + xmlrpc_DECREF(all_bugs); + } + } + free(product); + free(version); + + if (all_bugs_size < 0) + { + throw_if_xml_fault_occurred(&bz_server.env); + } + else if (all_bugs_size == 0) // Create new bug + { + update_client(_("Creating a new bug...")); + bug_id = bz_server.new_bug(pCrashData, depend_on_bugno); + if (bug_id < 0) + { + throw_if_xml_fault_occurred(&bz_server.env); + throw CABRTException(EXCEP_PLUGIN, _("Bugzilla entry creation failed")); + } + + log("Adding attachments to bug %d...", bug_id); + int ret = bz_server.add_attachments(to_string(bug_id).c_str(), pCrashData); + if (ret == -1) + { + throw_if_xml_fault_occurred(&bz_server.env); + } + + update_client(_("Logging out...")); + bz_server.logout(); + + std::string bug_status = ssprintf( + "Status: NEW\n" + "%s/show_bug.cgi?id=%u", + BugzillaURL.c_str(), + (int)bug_id + ); + return bug_status; + } + else if (all_bugs_size > 1) + { + // When someone clones bug it has same duphash, so we can find more than 1. + // Need to be checked if component is same. + VERB3 log("Bugzilla has %u reports with same duphash '%s'", all_bugs_size, duphash.c_str()); + } + + // decision based on state + update_client(_("Bug is already reported: %i"), bug_id); + + xmlrpc_int32 original_bug_id = bug_id; + if ((strcmp(bz.bug_status, "CLOSED") == 0) && (strcmp(bz.bug_resolution, "DUPLICATE") == 0)) + { + for (int ii = 0; ii <= MAX_HOPS; ii++) + { + if (ii == MAX_HOPS) + { + VERB3 log("Bugzilla could not find a parent of bug %d", (int)original_bug_id); + bug_info_destroy(&bz); + throw CABRTException(EXCEP_PLUGIN, _("Bugzilla couldn't find parent of bug %d"), (int)original_bug_id); + } + + log("Bug %d is a duplicate, using parent bug %d", bug_id, (int)bz.bug_dup_id); + bug_id = bz.bug_dup_id; + bug_info_destroy(&bz); + bug_info_init(&bz); + + if (bz_server.get_bug_info(&bz, bug_id) == -1) + { + bug_info_destroy(&bz); + if (bz_server.env.fault_occurred) + { + throw_if_xml_fault_occurred(&bz_server.env); + } + throw CABRTException(EXCEP_PLUGIN, _("get_bug_info() failed. Could not collect all mandatory information")); + } + + // found a bug which is not CLOSED as DUPLICATE + if (bz.bug_dup_id == -1) + break; + } + } + + if (strcmp(bz.bug_status, "CLOSED") != 0) + { + int status = 0; + if ((strcmp(bz.bug_reporter, Login.c_str()) != 0) && (am_i_in_cc(&bz, Login.c_str()))) + { + VERB2 log(_("Adding %s to CC list"), Login.c_str()); + update_client(_("Adding %s to CC list"), Login.c_str()); + status = bz_server.add_plus_one_cc(bug_id, Login.c_str()); + } + + if (status == -1) + { + bug_info_destroy(&bz); + throw_if_xml_fault_occurred(&bz_server.env); + } + + std::string description = make_description_reproduce_comment(pCrashData); + if (!description.empty()) + { + const char* package = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_PACKAGE); + const char* release = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_RELEASE); + const char* arch = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_ARCHITECTURE); + const char* is_private = get_crash_data_item_content_or_NULL(pCrashData, "is_private"); + + description = ssprintf("Package: %s\n" + "Architecture: %s\n" + "OS Release: %s\n" + "%s", package, arch, release, description.c_str() + ); + + update_client(_("Adding new comment to bug %d"), (int)bug_id); + + bool is_priv = is_private && (is_private[0] == '1'); + if (bz_server.add_comment(bug_id, description.c_str(), is_priv) == -1) + { + bug_info_destroy(&bz); + throw_if_xml_fault_occurred(&bz_server.env); + } + } + } + + update_client(_("Logging out...")); + bz_server.logout(); + + std::string bug_status = ssprintf( + "Status: %s%s%s\n" + "%s/show_bug.cgi?id=%u", + bz.bug_status, + bz.bug_resolution ? " " : "", + bz.bug_resolution ? bz.bug_resolution : "", + BugzillaURL.c_str(), + (int)bug_id + ); + + bug_info_destroy(&bz); + + return bug_status; +} + +void CReporterBugzilla::SetSettings(const map_plugin_settings_t& pSettings) +{ + m_pSettings = pSettings; + +//BUG! This gets called when user's keyring contains login data, +//then it takes precedence over /etc/abrt/plugins/Bugzilla.conf. +//I got a case when keyring had a STALE password, and there was no way +//for me to know that it is being used. Moreover, when I discovered it +//(by hacking abrt source!), I don't know how to purge it from the keyring. +//At the very least, log("SOMETHING") here. + + map_plugin_settings_t::const_iterator end = pSettings.end(); + map_plugin_settings_t::const_iterator it; + it = pSettings.find("BugzillaURL"); + if (it != end) + { + m_sBugzillaURL = it->second; + //remove the /xmlrpc.cgi part from old settings + //FIXME: can be removed after users are informed about new config format + std::string::size_type pos = m_sBugzillaURL.find(XML_RPC_SUFFIX); + if (pos != std::string::npos) + { + m_sBugzillaURL.erase(pos); + } + //remove the trailing '/' + while (m_sBugzillaURL[m_sBugzillaURL.length() - 1] == '/') + { + m_sBugzillaURL.erase(m_sBugzillaURL.length() - 1); + } + /* + if (*(--m_sBugzillaURL.end()) == '/') + { + m_sBugzillaURL.erase(--m_sBugzillaURL.end()); + } + */ + m_sBugzillaXMLRPC = m_sBugzillaURL + XML_RPC_SUFFIX; + } + it = pSettings.find("Login"); + if (it != end) + { + m_sLogin = it->second; + } + it = pSettings.find("Password"); + if (it != end) + { + m_sPassword = it->second; + } + it = pSettings.find("SSLVerify"); + if (it != end) + { + m_bSSLVerify = string_to_bool(it->second.c_str()); + } +} + +/* Should not be deleted (why?) */ +const map_plugin_settings_t& CReporterBugzilla::GetSettings() +{ + m_pSettings["BugzillaURL"] = m_sBugzillaURL; + m_pSettings["Login"] = m_sLogin; + m_pSettings["Password"] = m_sPassword; + m_pSettings["SSLVerify"] = m_bSSLVerify ? "yes" : "no"; + m_pSettings["RatingRequired"] = m_bRatingRequired ? "yes" : "no"; + + return m_pSettings; +} + +PLUGIN_INFO(REPORTER, + CReporterBugzilla, + "Bugzilla", + "0.0.4", + _("Reports bugs to bugzilla"), + "npajkovs@redhat.com", + "https://fedorahosted.org/abrt/wiki", + PLUGINS_LIB_DIR"/Bugzilla.glade"); diff --git a/lib/plugins/Bugzilla.glade b/lib/plugins/Bugzilla.glade new file mode 100644 index 00000000..cabdd06a --- /dev/null +++ b/lib/plugins/Bugzilla.glade @@ -0,0 +1,246 @@ + + + + + + 12 + False + True + center-on-parent + abrt + normal + False + + + True + vertical + 12 + + + True + 0 + none + + + True + 6 + 12 + + + True + 5 + 2 + 12 + 6 + + + True + 0 + Bugzilla URL: + + + GTK_FILL + + + + + True + 0 + Login(email): + + + 1 + 2 + GTK_FILL + + + + + True + 0 + Password: + + + 2 + 3 + GTK_FILL + + + + + True + True + + + + 1 + 2 + + + + + True + True + + + + 1 + 2 + 1 + 2 + + + + + True + True + False + + + + 1 + 2 + 2 + 3 + + + + + Show password + True + True + False + 0 + True + + + 1 + 2 + 3 + 4 + + + + + 0 + + + 3 + 4 + GTK_FILL + + + + + SSL verify + True + True + False + 0 + True + + + 2 + 4 + 5 + + + + + + + + + True + <b>Bugzilla plugin configuration</b> + True + + + + + False + False + 1 + + + + + True + + + True + True + 0 + 0 + Don't have an account yet? + True + + + 0 + + + + + True + 5 + You can create it <a href="https://bugzilla.redhat.com/createaccount.cgi">here</a> + True + True + + + 1 + + + + + 2 + + + + + True + end + + + gtk-cancel + True + True + True + True + + + False + False + 0 + + + + + gtk-apply + True + True + True + True + + + False + False + 1 + + + + + False + end + 0 + + + + + + button2 + bApply + + + + diff --git a/lib/plugins/Bugzilla.h b/lib/plugins/Bugzilla.h new file mode 100644 index 00000000..a4c9a01e --- /dev/null +++ b/lib/plugins/Bugzilla.h @@ -0,0 +1,47 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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 BUGZILLA_H_ +#define BUGZILLA_H_ + +#include "plugin.h" +#include "reporter.h" + +class CReporterBugzilla : public CReporter +{ + private: + bool m_bSSLVerify; + std::string m_sBugzillaURL; + std::string m_sBugzillaXMLRPC; + std::string m_sLogin; + std::string m_sPassword; + bool m_bRatingRequired; + + public: + CReporterBugzilla(); + virtual ~CReporterBugzilla(); + + virtual std::string Report(const map_crash_data_t& pCrashData, + const map_plugin_settings_t& pSettings, + const char *pArgs); + + virtual void SetSettings(const map_plugin_settings_t& pSettings); + virtual const map_plugin_settings_t& GetSettings(); +}; + +#endif /* BUGZILLA_H_ */ diff --git a/lib/plugins/CCpp.conf b/lib/plugins/CCpp.conf new file mode 100644 index 00000000..86bfb7db --- /dev/null +++ b/lib/plugins/CCpp.conf @@ -0,0 +1,42 @@ +# Configuration file for CCpp hook and plugin +Enabled = yes + +# If you also want to dump file named "core" +# in crashed process' current dir, set to "yes" +MakeCompatCore = yes + +# Do you want a copy of crashed binary be saved? +# (useful, for example, when _deleted binary_ segfaults) +SaveBinaryImage = no + +# Generate backtrace +Backtrace = yes + +# Generate backtrace for crashes uploaded from remote machines. +# Note that for reliable backtrace generation, your local machine +# needs to have the crashed executable and all libraries it uses, +# and they need to be the same versions as on remote machines. +# If you cannot ensure that, it's better to set this option to "no" +BacktraceRemotes = no + +# Generate memory map too (IGNORED FOR NOW) +MemoryMap = no + +# How to get debuginfo: install, mount +## install - download and install debuginfo packages +## mount - mount fedora NFS with debug info +## (IGNORED FOR NOW) +DebugInfo = install + +# If this option is set to "yes", +# debuginfos will be installed to @@LOCALSTATEDIR@@/cache/abrt-di +InstallDebugInfo = yes + +# Additional directories to search for debuginfos. +# For example, you can list a network-mounted shared store +# of all debuginfos here. +# ReadonlyLocalDebugInfoDirs = /path1:/path2:... + +# Keep @@LOCALSTATEDIR@@/cache/abrt-di +# from growing out-of-bounds. +DebugInfoCacheMB = 4000 diff --git a/lib/plugins/CCpp.cpp b/lib/plugins/CCpp.cpp new file mode 100644 index 00000000..629da665 --- /dev/null +++ b/lib/plugins/CCpp.cpp @@ -0,0 +1,1064 @@ +/* + CCpp.cpp + + Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) + Copyright (C) 2009 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 +#include "abrtlib.h" +#include "strbuf.h" +#include "CCpp.h" +#include "abrt_exception.h" +#include "debug_dump.h" +#include "comm_layer_inner.h" +#include "Polkit.h" +#include "backtrace.h" +#include "CCpp_sha1.h" + +using namespace std; + +#define CORE_PATTERN_IFACE "/proc/sys/kernel/core_pattern" +#define CORE_PATTERN "|"CCPP_HOOK_PATH" "DEBUG_DUMPS_DIR" %p %s %u %c" +#define CORE_PIPE_LIMIT_IFACE "/proc/sys/kernel/core_pipe_limit" +/* core_pipe_limit specifies how many dump_helpers might run at the same time +0 - means unlimited, but the it's not guaranteed that /proc/ of crashing +process might not be available for dump_helper +4 - means that 4 dump_helpers can run at the same time, which should be enough +for ABRT, we can miss some crashes, but what are the odds that more processes +crash at the same time? This value has been recommended by nhorman +*/ +#define CORE_PIPE_LIMIT "4" + +#define DEBUGINFO_CACHE_DIR LOCALSTATEDIR"/cache/abrt-di" + +CAnalyzerCCpp::CAnalyzerCCpp() : + m_bBacktrace(true), + m_bBacktraceRemotes(false), + m_bMemoryMap(false), + m_bInstallDebugInfo(true), + m_nDebugInfoCacheMB(4000) +{} + +static string create_hash(const char *pInput) +{ + unsigned int len; + +#if 0 +{ + char hash_str[SHA1_LENGTH*2 + 1]; + unsigned char hash[SHA1_LENGTH]; + HASHContext *hc; + hc = HASH_Create(HASH_AlgSHA1); + if (!hc) + { + error_msg_and_die("HASH_Create(HASH_AlgSHA1) failed"); /* paranoia */ + } + HASH_Begin(hc); + HASH_Update(hc, (const unsigned char*)pInput, strlen(pInput)); + HASH_End(hc, hash, &len, sizeof(hash)); + HASH_Destroy(hc); + + char *d = hash_str; + unsigned char *s = hash; + while (len) + { + *d++ = "0123456789abcdef"[*s >> 4]; + *d++ = "0123456789abcdef"[*s & 0xf]; + s++; + len--; + } + *d = '\0'; +//log("hash1:%s str:'%s'", hash_str, pInput); +} +#endif + + char hash_str[SHA1_RESULT_LEN*2 + 1]; + unsigned char hash2[SHA1_RESULT_LEN]; + sha1_ctx_t sha1ctx; + sha1_begin(&sha1ctx); + sha1_hash(pInput, strlen(pInput), &sha1ctx); + sha1_end(hash2, &sha1ctx); + len = SHA1_RESULT_LEN; + + char *d = hash_str; + unsigned char *s = hash2; + while (len) + { + *d++ = "0123456789abcdef"[*s >> 4]; + *d++ = "0123456789abcdef"[*s & 0xf]; + s++; + len--; + } + *d = '\0'; +//log("hash2:%s str:'%s'", hash_str, pInput); + + return hash_str; +} + +/* Returns status. See `man 2 wait` for status information. */ +static int ExecVP(char **pArgs, uid_t uid, int redirect_stderr, string& pOutput) +{ + /* Nuke everything which may make setlocale() switch to non-POSIX locale: + * we need to avoid having gdb output in some obscure language. + */ + static const char *const unsetenv_vec[] = { + "LANG", + "LC_ALL", + "LC_COLLATE", + "LC_CTYPE", + "LC_MESSAGES", + "LC_MONETARY", + "LC_NUMERIC", + "LC_TIME", + NULL + }; + + int flags = EXECFLG_INPUT_NUL | EXECFLG_OUTPUT | EXECFLG_SETGUID | EXECFLG_SETSID | EXECFLG_QUIET; + if (redirect_stderr) + flags |= EXECFLG_ERR2OUT; + VERB1 flags &= ~EXECFLG_QUIET; + + int pipeout[2]; + pid_t child = fork_execv_on_steroids(flags, pArgs, pipeout, (char**)unsetenv_vec, /*dir:*/ NULL, uid); + + /* We use this function to run gdb and unstrip. Bugs in gdb or corrupted + * coredumps were observed to cause gdb to enter infinite loop. + * Therefore we have a (largish) timeout, after which we kill the child. + */ + int t = time(NULL); /* int is enough, no need to use time_t */ + int endtime = t + 60; + while (1) + { + int timeout = endtime - t; + if (timeout < 0) + { + kill(child, SIGKILL); + pOutput += "\nTimeout exceeded: 60 second, killing "; + pOutput += pArgs[0]; + pOutput += "\n"; + break; + } + + /* We don't check poll result - checking read result is enough */ + struct pollfd pfd; + pfd.fd = pipeout[0]; + pfd.events = POLLIN; + poll(&pfd, 1, timeout * 1000); + + char buff[1024]; + int r = read(pipeout[0], buff, sizeof(buff) - 1); + if (r <= 0) + break; + buff[r] = '\0'; + pOutput += buff; + t = time(NULL); + } + close(pipeout[0]); + + int status; + waitpid(child, &status, 0); /* prevent having zombie child process */ + + return status; +} + +static void GetBacktrace(const char *pDebugDumpDir, + const char *pDebugInfoDirs, + string& pBacktrace) +{ + update_client(_("Generating backtrace")); + + string UID; + string executable; + { + CDebugDump dd; + dd.Open(pDebugDumpDir); + dd.LoadText(FILENAME_EXECUTABLE, executable); + dd.LoadText(CD_UID, UID); + } + + // Workaround for + // http://sourceware.org/bugzilla/show_bug.cgi?id=9622 + unsetenv("TERM"); + // This is not necessary, and was observed to cause + // environmant corruption (because we run in a thread?): + //putenv((char*)"TERM=dumb"); + + char *args[21]; + args[0] = (char*)"gdb"; + args[1] = (char*)"-batch"; + + // when/if gdb supports it: + // (https://bugzilla.redhat.com/show_bug.cgi?id=528668): + args[2] = (char*)"-ex"; + string dfd = "set debug-file-directory /usr/lib/debug"; + const char *p = pDebugInfoDirs; + while (1) + { + const char *colon_or_nul = strchrnul(p, ':'); + dfd += ':'; + dfd.append(p, colon_or_nul - p); + dfd += "/usr/lib/debug"; + if (*colon_or_nul != ':') + break; + p = colon_or_nul + 1; + } + args[3] = (char*)dfd.c_str(); + + /* "file BINARY_FILE" is needed, without it gdb cannot properly + * unwind the stack. Currently the unwind information is located + * in .eh_frame which is stored only in binary, not in coredump + * or debuginfo. + * + * Fedora GDB does not strictly need it, it will find the binary + * by its build-id. But for binaries either without build-id + * (= built on non-Fedora GCC) or which do not have + * their debuginfo rpm installed gdb would not find BINARY_FILE + * so it is still makes sense to supply "file BINARY_FILE". + * + * Unfortunately, "file BINARY_FILE" doesn't work well if BINARY_FILE + * was deleted (as often happens during system updates): + * gdb uses specified BINARY_FILE + * even if it is completely unrelated to the coredump. + * See https://bugzilla.redhat.com/show_bug.cgi?id=525721 + * + * TODO: check mtimes on COREFILE and BINARY_FILE and not supply + * BINARY_FILE if it is newer (to at least avoid gdb complaining). + */ + args[4] = (char*)"-ex"; + string file = ssprintf("file %s", executable.c_str()); + args[5] = (char*)file.c_str(); + + args[6] = (char*)"-ex"; + string corefile = ssprintf("core-file %s/"FILENAME_COREDUMP, pDebugDumpDir); + args[7] = (char*)corefile.c_str(); + + args[8] = (char*)"-ex"; + /*args[9] = ... see below */ + args[10] = (char*)"-ex"; + args[11] = (char*)"info sharedlib"; + /* glibc's abort() stores its message in __abort_msg variable */ + args[12] = (char*)"-ex"; + args[13] = (char*)"print (char*)__abort_msg"; + args[14] = (char*)"-ex"; + args[15] = (char*)"print (char*)__glib_assert_msg"; + args[16] = (char*)"-ex"; + args[17] = (char*)"info registers"; + args[18] = (char*)"-ex"; + args[19] = (char*)"disassemble"; + args[20] = NULL; + + /* Get the backtrace, but try to cap its size */ + /* Limit bt depth. With no limit, gdb sometimes OOMs the machine */ + unsigned bt_depth = 2048; + const char *thread_apply_all = "thread apply all "; + const char *full = " full"; + while (1) + { + string cmd = ssprintf("%sbacktrace %u%s", thread_apply_all, bt_depth, full); + args[9] = (char*)cmd.c_str(); + pBacktrace = ""; + ExecVP(args, xatoi_u(UID.c_str()), /*redirect_stderr:*/ 1, pBacktrace); + if (bt_depth <= 64 || pBacktrace.size() < 256*1024) + return; + bt_depth /= 2; + if (bt_depth <= 64 && thread_apply_all[0] != '\0') + { + /* This program likely has gazillion threads, dont try to bt them all */ + bt_depth = 256; + thread_apply_all = ""; + } + if (bt_depth <= 64 && full[0] != '\0') + { + /* Looks like there are gigantic local structures or arrays, disable "full" bt */ + bt_depth = 256; + full = ""; + } + } +} + +static void GetIndependentBuildIdPC(const char *unstrip_n_output, + string& pIndependentBuildIdPC) +{ + // lines look like this: + // 0x400000+0x209000 23c77451cf6adff77fc1f5ee2a01d75de6511dda@0x40024c - - [exe] + // 0x400000+0x209000 ab3c8286aac6c043fd1bb1cc2a0b88ec29517d3e@0x40024c /bin/sleep /usr/lib/debug/bin/sleep.debug [exe] + // 0x7fff313ff000+0x1000 389c7475e3d5401c55953a425a2042ef62c4c7df@0x7fff313ff2f8 . - linux-vdso.so.1 + const char *line = unstrip_n_output; + while (*line) + { + const char *eol = strchrnul(line, '\n'); + const char *plus = (char*)memchr(line, '+', eol - line); + if (plus) + { + while (++plus < eol && *plus != '@') + { + if (!isspace(*plus)) + { + pIndependentBuildIdPC += *plus; + } + } + } + if (*eol != '\n') break; + line = eol + 1; + } +} + +static string run_unstrip_n(const char *pDebugDumpDir) +{ + string UID; + { + CDebugDump dd; + dd.Open(pDebugDumpDir); + dd.LoadText(CD_UID, UID); + } + + char* args[4]; + args[0] = (char*)"eu-unstrip"; + args[1] = xasprintf("--core=%s/"FILENAME_COREDUMP, pDebugDumpDir); + args[2] = (char*)"-n"; + args[3] = NULL; + + string output; + ExecVP(args, xatoi_u(UID.c_str()), /*redirect_stderr:*/ 0, output); + + free(args[1]); + + return output; +} + +/* Needs gdb feature from here: https://bugzilla.redhat.com/show_bug.cgi?id=528668 + * It is slated to be in F12/RHEL6. + */ +static void InstallDebugInfos(const char *pDebugDumpDir, + const char *debuginfo_dirs, + string& build_ids) +{ + update_client(_("Starting the debuginfo installation")); + + int pipeout[2]; //TODO: can we use ExecVP? + xpipe(pipeout); + + fflush(NULL); + pid_t child = fork(); + if (child < 0) + { + /*close(pipeout[0]); - why bother */ + /*close(pipeout[1]); */ + perror_msg_and_die("fork"); + } + if (child == 0) + { + close(pipeout[0]); + xmove_fd(pipeout[1], STDOUT_FILENO); + xmove_fd(xopen("/dev/null", O_RDONLY), STDIN_FILENO); + + char *coredump = xasprintf("%s/"FILENAME_COREDUMP, pDebugDumpDir); + /* SELinux guys are not happy with /tmp, using /var/run/abrt */ + char *tempdir = xasprintf(LOCALSTATEDIR"/run/abrt/tmp-%lu-%lu", (long)getpid(), (long)time(NULL)); + /* log() goes to stderr/syslog, it's ok to use it here */ + VERB1 log("Executing: %s %s %s %s", "abrt-debuginfo-install", coredump, tempdir, debuginfo_dirs); + /* We want parent to see errors in the same stream */ + xdup2(STDOUT_FILENO, STDERR_FILENO); + execlp("abrt-debuginfo-install", "abrt-debuginfo-install", coredump, tempdir, debuginfo_dirs, NULL); + perror_msg("Can't execute '%s'", "abrt-debuginfo-install"); + /* Serious error (1 means "some debuginfos not found") */ + exit(2); + } + + close(pipeout[1]); + + FILE *pipeout_fp = fdopen(pipeout[0], "r"); + if (pipeout_fp == NULL) /* never happens */ + { + close(pipeout[0]); + waitpid(child, NULL, 0); + return; + } + + /* With 126 debuginfos I've seen lines 9k+ chars long... + * yet, having it truly unlimited is bad too, + * therefore we are using LARGE, but still limited buffer. + */ + char *buff = (char*) xmalloc(64*1024); + while (fgets(buff, 64*1024, pipeout_fp)) + { + strchrnul(buff, '\n')[0] = '\0'; + + if (strncmp(buff, "MISSING:", 8) == 0) + { + build_ids += "Debuginfo absent: "; + build_ids += buff + 8; + build_ids += "\n"; + continue; + } + + const char *p = buff; + while (*p == ' ' || *p == '\t') + { + p++; + } + if (*p) + { + VERB1 log("%s", buff); + update_client("%s", buff); + } + } + free(buff); + fclose(pipeout_fp); + + int status = 0; + while (waitpid(child, &status, 0) < 0 && errno == EINTR) + continue; + if (WIFEXITED(status)) + { + if (WEXITSTATUS(status) > 1) + error_msg("%s exited with %u", "abrt-debuginfo-install", (int)WEXITSTATUS(status)); + } + else + { + error_msg("%s killed by signal %u", "abrt-debuginfo-install", (int)WTERMSIG(status)); + } +} + +static double get_dir_size(const char *dirname, + string *worst_file, + double *maxsz) +{ + DIR *dp = opendir(dirname); + if (dp == NULL) + return 0; + + struct dirent *ep; + struct stat stats; + double size = 0; + while ((ep = readdir(dp)) != NULL) + { + if (dot_or_dotdot(ep->d_name)) + continue; + string dname = concat_path_file(dirname, ep->d_name); + if (lstat(dname.c_str(), &stats) != 0) + continue; + if (S_ISDIR(stats.st_mode)) + { + double sz = get_dir_size(dname.c_str(), worst_file, maxsz); + size += sz; + } + else if (S_ISREG(stats.st_mode)) + { + double sz = stats.st_size; + size += sz; + + if (worst_file) + { + /* Calculate "weighted" size and age + * w = sz_kbytes * age_mins */ + sz /= 1024; + long age = (time(NULL) - stats.st_mtime) / 60; + if (age > 0) + sz *= age; + + if (sz > *maxsz) + { + *maxsz = sz; + *worst_file = dname; + } + } + } + } + closedir(dp); + return size; +} + +static void trim_debuginfo_cache(unsigned max_mb) +{ + while (1) + { + string worst_file; + double maxsz = 0; + double cache_sz = get_dir_size(DEBUGINFO_CACHE_DIR, &worst_file, &maxsz); + if (cache_sz / (1024 * 1024) < max_mb) + break; + VERB1 log("%s is %.0f bytes (over %u MB), deleting '%s'", + DEBUGINFO_CACHE_DIR, cache_sz, max_mb, worst_file.c_str()); + if (unlink(worst_file.c_str()) != 0) + perror_msg("Can't unlink '%s'", worst_file.c_str()); + } +} + +string CAnalyzerCCpp::GetLocalUUID(const char *pDebugDumpDir) +{ + string executable; + string package; + { + CDebugDump dd; + dd.Open(pDebugDumpDir); + dd.LoadText(FILENAME_EXECUTABLE, executable); + dd.LoadText(FILENAME_PACKAGE, package); + } + + string unstrip_n_output = run_unstrip_n(pDebugDumpDir); + string independentBuildIdPC; + GetIndependentBuildIdPC(unstrip_n_output.c_str(), independentBuildIdPC); + + /* package variable has "firefox-3.5.6-1.fc11[.1]" format */ + /* Remove distro suffix and maybe least significant version number */ + char *trimmed_package = xstrdup(package.c_str()); + char *p = trimmed_package; + while (*p) + { + if (*p == '.' && (p[1] < '0' || p[1] > '9')) + { + /* We found "XXXX.nondigitXXXX", trim this part */ + *p = '\0'; + break; + } + p++; + } + char *first_dot = strchr(trimmed_package, '.'); + if (first_dot) + { + char *last_dot = strrchr(first_dot, '.'); + if (last_dot != first_dot) + { + /* There are more than one dot: "1.2.3" + * Strip last part, we don't want to distinquish crashes + * in packages which differ only by minor release number. + */ + *last_dot = '\0'; + } + } + string hash_str = trimmed_package + executable + independentBuildIdPC; + free(trimmed_package); + return create_hash(hash_str.c_str()); +} + +string CAnalyzerCCpp::GetGlobalUUID(const char *pDebugDumpDir) +{ + CDebugDump dd; + dd.Open(pDebugDumpDir); + if (dd.Exist(FILENAME_GLOBAL_UUID)) + { + string uuid; + dd.LoadText(FILENAME_GLOBAL_UUID, uuid); + return uuid; + } + else + { + // Compatibility code. + // This whole block should be deleted for Fedora 14. + log(_("Getting global universal unique identification...")); + + string backtrace_path = concat_path_file(pDebugDumpDir, FILENAME_BACKTRACE); + string executable; + string package; + string uid_str; + dd.LoadText(FILENAME_EXECUTABLE, executable); + dd.LoadText(FILENAME_PACKAGE, package); + if (m_bBacktrace) + dd.LoadText(CD_UID, uid_str); + + string independent_backtrace; + if (m_bBacktrace) + { + /* Run abrt-backtrace to get independent backtrace suitable + to UUID calculation. */ + char *args[7]; + args[0] = (char*)"abrt-backtrace"; + args[1] = (char*)"--single-thread"; + args[2] = (char*)"--remove-exit-handlers"; + args[3] = (char*)"--frame-depth=5"; + args[4] = (char*)"--remove-noncrash-frames"; + args[5] = (char*)backtrace_path.c_str(); + args[6] = NULL; + + int pipeout[2]; + xpipe(pipeout); /* stdout of abrt-backtrace */ + + fflush(NULL); + pid_t child = fork(); + if (child == -1) + perror_msg_and_die("fork"); + if (child == 0) + { + VERB1 log("Executing %s", args[0]); + + xmove_fd(pipeout[1], STDOUT_FILENO); + close(pipeout[0]); /* read side of the pipe */ + + /* abrt-backtrace is executed under the user's uid and gid. */ + uid_t uid = xatoi_u(uid_str.c_str()); + struct passwd* pw = getpwuid(uid); + gid_t gid = pw ? pw->pw_gid : uid; + setgroups(1, &gid); + xsetregid(gid, gid); + xsetreuid(uid, uid); + + execvp(args[0], args); + VERB1 perror_msg("Can't execute '%s'", args[0]); + exit(1); + } + + close(pipeout[1]); /* write side of the pipe */ + + /* Read the result from abrt-backtrace. */ + int r; + char buff[1024]; + while ((r = safe_read(pipeout[0], buff, sizeof(buff) - 1)) > 0) + { + buff[r] = '\0'; + independent_backtrace += buff; + } + close(pipeout[0]); + + /* Wait until it exits, and check the exit status. */ + errno = 0; + int status; + waitpid(child, &status, 0); + if (!WIFEXITED(status)) + { + perror_msg("abrt-backtrace not executed properly, " + "status: %x signal: %d", status, WIFSIGNALED(status)); + } + else + { + int exit_status = WEXITSTATUS(status); + if (exit_status == 79) /* EX_PARSINGFAILED */ + { + /* abrt-backtrace returns alternative backtrace + representation in this case, so everything will work + as expected except worse duplication detection */ + log_msg("abrt-backtrace failed to parse the backtrace"); + } + else if (exit_status == 80) /* EX_THREADDETECTIONFAILED */ + { + /* abrt-backtrace returns backtrace with all threads + in this case, so everything will work as expected + except worse duplication detection */ + log_msg("abrt-backtrace failed to determine crash frame"); + } + else if (exit_status != 0) + { + /* this is unexpected problem and it should be investigated */ + error_msg("abrt-backtrace run failed, exit value: %d", + exit_status); + } + } + + /*VERB1 log("abrt-backtrace result: %s", independent_backtrace.c_str());*/ + } + /* else: no backtrace, independent_backtrace == "" + no backtrace => rating = 0 + */ + else + { + dd.SaveText(FILENAME_RATING, "0"); + } + + string hash_base = package + executable + independent_backtrace; + return create_hash(hash_base.c_str()); + } +} + +static bool DebuginfoCheckPolkit(uid_t uid) +{ + fflush(NULL); + int child_pid = fork(); + if (child_pid < 0) + { + perror_msg_and_die("fork"); + } + if (child_pid == 0) + { + //child + xsetreuid(uid, uid); + PolkitResult result = polkit_check_authorization(getpid(), + "org.fedoraproject.abrt.install-debuginfos"); + exit(result != PolkitYes); //exit 1 (failure) if not allowed + } + + //parent + int status; + if (waitpid(child_pid, &status, 0) > 0 + && WIFEXITED(status) + && WEXITSTATUS(status) == 0 + ) { + return true; //authorization OK + } + log("UID %d is not authorized to install debuginfos", uid); + return false; +} + +void CAnalyzerCCpp::CreateReport(const char *pDebugDumpDir, int force) +{ + string package, executable, UID; + + CDebugDump dd; + dd.Open(pDebugDumpDir); + + /* Skip remote crashes. */ + if (dd.Exist(FILENAME_REMOTE)) + { + std::string remote_str; + dd.LoadText(FILENAME_REMOTE, remote_str); + bool remote = (remote_str.find('1') != std::string::npos); + if (remote && !m_bBacktraceRemotes) + return; + } + + if (!m_bBacktrace) + return; + + if (!force) + { + bool bt_exists = dd.Exist(FILENAME_BACKTRACE); + if (bt_exists) + return; /* backtrace already exists */ + } + + dd.LoadText(FILENAME_PACKAGE, package); + dd.LoadText(FILENAME_EXECUTABLE, executable); + dd.LoadText(CD_UID, UID); + dd.Close(); /* do not keep dir locked longer than needed */ + + string build_ids; + if (m_bInstallDebugInfo && DebuginfoCheckPolkit(xatoi_u(UID.c_str()))) + { + if (m_nDebugInfoCacheMB > 0) + trim_debuginfo_cache(m_nDebugInfoCacheMB); + InstallDebugInfos(pDebugDumpDir, m_sDebugInfoDirs.c_str(), build_ids); + } + else + VERB1 log(_("Skipping the debuginfo installation")); + + /* Create and store backtrace. */ + string backtrace_str; + GetBacktrace(pDebugDumpDir, m_sDebugInfoDirs.c_str(), backtrace_str); + dd.Open(pDebugDumpDir); + dd.SaveText(FILENAME_BACKTRACE, (backtrace_str + build_ids).c_str()); + + if (m_bMemoryMap) + dd.SaveText(FILENAME_MEMORYMAP, "memory map of the crashed C/C++ application, not implemented yet"); + + /* Compute and store UUID from the backtrace. */ + char *backtrace_cpy = xstrdup(backtrace_str.c_str()); + struct backtrace *backtrace = backtrace_parse(backtrace_cpy, false, false); + free(backtrace_cpy); + if (backtrace) + { + /* Get the quality of the full backtrace. */ + float q1 = backtrace_quality(backtrace); + + /* Remove all the other threads except the crash thread. */ + struct thread *crash_thread = backtrace_find_crash_thread(backtrace); + if (crash_thread) + backtrace_remove_threads_except_one(backtrace, crash_thread); + else + log_msg("Detection of crash thread failed"); + + /* Get the quality of the crash thread. */ + float q2 = backtrace_quality(backtrace); + + backtrace_remove_noncrash_frames(backtrace); + + /* Do the frame removal now. */ + backtrace_limit_frame_depth(backtrace, 5); + /* Frame removal can be done before removing exit handlers. */ + backtrace_remove_exit_handlers(backtrace); + + /* Get the quality of frames around the crash. */ + float q3 = backtrace_quality(backtrace); + + /* Compute UUID. */ + struct strbuf *bt = backtrace_tree_as_str(backtrace, false); + strbuf_prepend_str(bt, executable.c_str()); + strbuf_prepend_str(bt, package.c_str()); + dd.SaveText(FILENAME_GLOBAL_UUID, create_hash(bt->buf).c_str()); + strbuf_free(bt); + + /* Compute and store backtrace rating. */ + /* Compute and store backtrace rating. The crash frame + is more important that the others. The frames around + the crash are more important than the rest. */ + float qtot = 0.25f * q1 + 0.35f * q2 + 0.4f * q3; + + /* Turn the quality to rating. */ + const char *rating; + if (qtot < 0.6f) rating = "0"; + else if (qtot < 0.7f) rating = "1"; + else if (qtot < 0.8f) rating = "2"; + else if (qtot < 0.9f) rating = "3"; + else rating = "4"; + dd.SaveText(FILENAME_RATING, rating); + + /* Get the function name from the crash frame. */ + if (crash_thread) + { + struct frame *crash_frame = crash_thread->frames; + struct frame *abort_frame = thread_find_abort_frame(crash_thread); + if (abort_frame) + crash_frame = abort_frame->next; + if (crash_frame && crash_frame->function && 0 != strcmp(crash_frame->function, "??")) + dd.SaveText(FILENAME_CRASH_FUNCTION, crash_frame->function); + } + + backtrace_free(backtrace); + } + else + { + /* If the parser failed fall back to the independent backtrace. */ + /* If we write and use a hand-written parser instead of the bison one, + the parser never fails, and it will be possible to get rid of + the independent_backtrace and backtrace_rate_old. */ + struct strbuf *ibt = independent_backtrace(backtrace_str.c_str()); + strbuf_prepend_str(ibt, executable.c_str()); + strbuf_prepend_str(ibt, package.c_str()); + dd.SaveText(FILENAME_GLOBAL_UUID, create_hash(ibt->buf).c_str()); + strbuf_free(ibt); + + /* Compute and store backtrace rating. */ + /* Crash frame is not known so store nothing. */ + dd.SaveText(FILENAME_RATING, to_string(backtrace_rate_old(backtrace_str.c_str())).c_str()); + } + + dd.Close(); +} + +/* + this is just a workaround until kernel changes it's behavior + when handling pipes in core_pattern +*/ +#ifdef HOSTILE_KERNEL +#define CORE_SIZE_PATTERN "Max core file size=1:unlimited" +static int isdigit_str(char *str) +{ + do { + if (*str < '0' || *str > '9') + return 0; + } while (*++str); + return 1; +} + +static int set_limits() +{ + DIR *dir = opendir("/proc"); + if (!dir) { + /* this shouldn't fail, but to be safe.. */ + return 1; + } + + struct dirent *ent; + while ((ent = readdir(dir)) != NULL) { + if (!isdigit_str(ent->d_name)) + continue; + + char limits_name[sizeof("/proc/%s/limits") + sizeof(long)*3]; + snprintf(limits_name, sizeof(limits_name), "/proc/%s/limits", ent->d_name); + FILE *limits_fp = fopen(limits_name, "r"); + if (!limits_fp) { + break; + } + + char line[128]; + char *ulimit_c = NULL; + while (1) { + if (fgets(line, sizeof(line)-1, limits_fp) == NULL) + break; + if (strncmp(line, "Max core file size", sizeof("Max core file size")-1) == 0) { + ulimit_c = skip_whitespace(line + sizeof("Max core file size")-1); + skip_non_whitespace(ulimit_c)[0] = '\0'; + break; + } + } + fclose(limits_fp); + if (!ulimit_c || ulimit_c[0] != '0' || ulimit_c[1] != '\0') { + /*process has nonzero ulimit -c, so need to modify it*/ + continue; + } + /* echo -n 'Max core file size=1:unlimited' >/proc/PID/limits */ + int fd = open(limits_name, O_WRONLY); + if (fd >= 0) { + errno = 0; + /*full_*/ + ssize_t n = write(fd, CORE_SIZE_PATTERN, sizeof(CORE_SIZE_PATTERN)-1); + if (n < sizeof(CORE_SIZE_PATTERN)-1) + log("warning: can't write core_size limit to: %s", limits_name); + close(fd); + } + else + { + log("warning: can't open %s for writing", limits_name); + } + } + closedir(dir); + return 0; +} +#endif /* HOSTILE_KERNEL */ + +void CAnalyzerCCpp::Init() +{ + FILE *fp = fopen(CORE_PATTERN_IFACE, "r"); + if (fp) + { + char line[PATH_MAX]; + if (fgets(line, sizeof(line), fp)) + m_sOldCorePattern = line; + fclose(fp); + } + if (m_sOldCorePattern[0] == '|') + { + if (m_sOldCorePattern == CORE_PATTERN) + { + log("warning: %s already contains %s, " + "did abrt daemon crash recently?", + CORE_PATTERN_IFACE, CORE_PATTERN); + /* There is no point in "restoring" CORE_PATTERN_IFACE + * to CORE_PATTERN on exit. Will restore to a default value: + */ + m_sOldCorePattern = "core"; + } else { + log("warning: %s was already set to run a crash analyser (%s), " + "abrt may interfere with it", + CORE_PATTERN_IFACE, CORE_PATTERN); + } + } +#ifdef HOSTILE_KERNEL + if (set_limits() != 0) + log("warning: failed to set core_size limit, ABRT won't detect crashes in" + "compiled apps"); +#endif + + fp = fopen(CORE_PATTERN_IFACE, "w"); + if (fp) + { + fputs(CORE_PATTERN, fp); + fclose(fp); + } + + /* read the core_pipe_limit and change it if it's == 0 + otherwise the abrt-hook-ccpp won't be able to read /proc/ + of the crashing process + */ + fp = fopen(CORE_PIPE_LIMIT_IFACE, "r"); + if (fp) + { + /* we care only about the first char, if it's + * not '0' then we don't have to change it, + * because it means that it's already != 0 + */ + char pipe_limit[2]; + if (!fgets(pipe_limit, sizeof(pipe_limit), fp)) + pipe_limit[0] = '1'; /* not 0 */ + fclose(fp); + if (pipe_limit[0] == '0') + { + fp = fopen(CORE_PIPE_LIMIT_IFACE, "w"); + if (fp) + { + fputs(CORE_PIPE_LIMIT, fp); + fclose(fp); + } + else + { + log("warning: failed to set core_pipe_limit, ABRT won't detect" + "crashes in compiled apps if kernel > 2.6.31"); + } + } + } +} + +void CAnalyzerCCpp::DeInit() +{ + /* no need to restore the core_pipe_limit, because it's only used + when there is s pipe in core_pattern + */ + FILE *fp = fopen(CORE_PATTERN_IFACE, "w"); + if (fp) + { + fputs(m_sOldCorePattern.c_str(), fp); + fclose(fp); + } +} + +void CAnalyzerCCpp::SetSettings(const map_plugin_settings_t& pSettings) +{ + m_pSettings = pSettings; + + map_plugin_settings_t::const_iterator end = pSettings.end(); + map_plugin_settings_t::const_iterator it; + it = pSettings.find("Backtrace"); + if (it != end) + { + m_bBacktrace = string_to_bool(it->second.c_str()); + } + it = pSettings.find("BacktraceRemotes"); + if (it != end) + { + m_bBacktraceRemotes = string_to_bool(it->second.c_str()); + } + it = pSettings.find("MemoryMap"); + if (it != end) + { + m_bMemoryMap = string_to_bool(it->second.c_str()); + } + it = pSettings.find("DebugInfo"); + if (it != end) + { + m_sDebugInfo = it->second; + } + it = pSettings.find("DebugInfoCacheMB"); + if (it != end) + { + m_nDebugInfoCacheMB = xatou(it->second.c_str()); + } + it = pSettings.find("InstallDebugInfo"); + if (it == end) //compat, remove after 0.0.11 + it = pSettings.find("InstallDebuginfo"); + if (it != end) + { + m_bInstallDebugInfo = string_to_bool(it->second.c_str()); + } + m_sDebugInfoDirs = DEBUGINFO_CACHE_DIR; + it = pSettings.find("ReadonlyLocalDebugInfoDirs"); + if (it != end) + { + m_sDebugInfoDirs += ':'; + m_sDebugInfoDirs += it->second; + } +} + +//ok to delete? +//const map_plugin_settings_t& CAnalyzerCCpp::GetSettings() +//{ +// m_pSettings["MemoryMap"] = m_bMemoryMap ? "yes" : "no"; +// m_pSettings["DebugInfo"] = m_sDebugInfo; +// m_pSettings["DebugInfoCacheMB"] = to_string(m_nDebugInfoCacheMB); +// m_pSettings["InstallDebugInfo"] = m_bInstallDebugInfo ? "yes" : "no"; +// +// return m_pSettings; +//} + +PLUGIN_INFO(ANALYZER, + CAnalyzerCCpp, + "CCpp", + "0.0.1", + _("Analyzes crashes in C/C++ programs"), + "zprikryl@redhat.com", + "https://fedorahosted.org/abrt/wiki", + ""); diff --git a/lib/plugins/CCpp.h b/lib/plugins/CCpp.h new file mode 100644 index 00000000..f547686f --- /dev/null +++ b/lib/plugins/CCpp.h @@ -0,0 +1,53 @@ +/* + CCpp.h - header file for C/C++ analyzer plugin + - it can get UUID and memory maps from core files + + Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) + Copyright (C) 2009 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 CCPP_H_ +#define CCPP_H_ + +#include +#include "plugin.h" +#include "analyzer.h" + +class CAnalyzerCCpp : public CAnalyzer +{ + private: + bool m_bBacktrace; + bool m_bBacktraceRemotes; + bool m_bMemoryMap; + bool m_bInstallDebugInfo; + unsigned m_nDebugInfoCacheMB; + std::string m_sOldCorePattern; + std::string m_sDebugInfo; + std::string m_sDebugInfoDirs; + + public: + CAnalyzerCCpp(); + virtual std::string GetLocalUUID(const char *pDebugDumpDir); + virtual std::string GetGlobalUUID(const char *pDebugDumpDir); + virtual void CreateReport(const char *pDebugDumpDir, int force); + virtual void Init(); + virtual void DeInit(); + virtual void SetSettings(const map_plugin_settings_t& pSettings); +//ok to delete? +// virtual const map_plugin_settings_t& GetSettings(); +}; + +#endif /* CCPP */ diff --git a/lib/plugins/CCpp_sha1.cpp b/lib/plugins/CCpp_sha1.cpp new file mode 100644 index 00000000..86a9e831 --- /dev/null +++ b/lib/plugins/CCpp_sha1.cpp @@ -0,0 +1,214 @@ +/* vi: set sw=4 ts=4: */ +/* + * Based on shasum from http://www.netsw.org/crypto/hash/ + * Majorly hacked up to use Dr Brian Gladman's sha1 code + * + * Copyright (C) 2002 Dr Brian Gladman , Worcester, UK. + * Copyright (C) 2003 Glenn L. McGrath + * Copyright (C) 2003 Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * --------------------------------------------------------------------------- + * Issue Date: 10/11/2002 + * + * This is a byte oriented version of SHA1 that operates on arrays of bytes + * stored in memory. It runs at 22 cycles per byte on a Pentium P4 processor + * + * --------------------------------------------------------------------------- + */ +#include "abrtlib.h" +#include "CCpp_sha1.h" + +#if defined(__BIG_ENDIAN__) && __BIG_ENDIAN__ +# define SHA1_BIG_ENDIAN 1 +# define SHA1_LITTLE_ENDIAN 0 +#elif __BYTE_ORDER == __BIG_ENDIAN +# define SHA1_BIG_ENDIAN 1 +# define SHA1_LITTLE_ENDIAN 0 +#elif __BYTE_ORDER == __LITTLE_ENDIAN +# define SHA1_BIG_ENDIAN 0 +# define SHA1_LITTLE_ENDIAN 1 +#else +# error "Can't determine endianness" +#endif + + +#define rotl32(x,n) (((x) << (n)) | ((x) >> (32 - (n)))) +#define rotr32(x,n) (((x) >> (n)) | ((x) << (32 - (n)))) +/* for sha512: */ +#define rotr64(x,n) (((x) >> (n)) | ((x) << (64 - (n)))) +#if SHA1_LITTLE_ENDIAN +static inline uint64_t hton64(uint64_t v) +{ + return (((uint64_t)htonl(v)) << 32) | htonl(v >> 32); +} +#else +#define hton64(v) (v) +#endif +#define ntoh64(v) hton64(v) + +/* To check alignment gcc has an appropriate operator. Other + compilers don't. */ +#if defined(__GNUC__) && __GNUC__ >= 2 +# define UNALIGNED_P(p,type) (((uintptr_t) p) % __alignof__(type) != 0) +#else +# define UNALIGNED_P(p,type) (((uintptr_t) p) % sizeof(type) != 0) +#endif + + +/* Some arch headers have conflicting defines */ +#undef ch +#undef parity +#undef maj +#undef rnd + +static void sha1_process_block64(sha1_ctx_t *ctx) +{ + unsigned t; + uint32_t W[80], a, b, c, d, e; + const uint32_t *words = (uint32_t*) ctx->wbuffer; + + for (t = 0; t < 16; ++t) { + W[t] = ntohl(*words); + words++; + } + + for (/*t = 16*/; t < 80; ++t) { + uint32_t T = W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16]; + W[t] = rotl32(T, 1); + } + + a = ctx->hash[0]; + b = ctx->hash[1]; + c = ctx->hash[2]; + d = ctx->hash[3]; + e = ctx->hash[4]; + +/* Reverse byte order in 32-bit words */ +#define ch(x,y,z) ((z) ^ ((x) & ((y) ^ (z)))) +#define parity(x,y,z) ((x) ^ (y) ^ (z)) +#define maj(x,y,z) (((x) & (y)) | ((z) & ((x) | (y)))) +/* A normal version as set out in the FIPS. This version uses */ +/* partial loop unrolling and is optimised for the Pentium 4 */ +#define rnd(f,k) \ + do { \ + uint32_t T = a; \ + a = rotl32(a, 5) + f(b, c, d) + e + k + W[t]; \ + e = d; \ + d = c; \ + c = rotl32(b, 30); \ + b = T; \ + } while (0) + + for (t = 0; t < 20; ++t) + rnd(ch, 0x5a827999); + + for (/*t = 20*/; t < 40; ++t) + rnd(parity, 0x6ed9eba1); + + for (/*t = 40*/; t < 60; ++t) + rnd(maj, 0x8f1bbcdc); + + for (/*t = 60*/; t < 80; ++t) + rnd(parity, 0xca62c1d6); +#undef ch +#undef parity +#undef maj +#undef rnd + + ctx->hash[0] += a; + ctx->hash[1] += b; + ctx->hash[2] += c; + ctx->hash[3] += d; + ctx->hash[4] += e; +} + +void sha1_begin(sha1_ctx_t *ctx) +{ + ctx->hash[0] = 0x67452301; + ctx->hash[1] = 0xefcdab89; + ctx->hash[2] = 0x98badcfe; + ctx->hash[3] = 0x10325476; + ctx->hash[4] = 0xc3d2e1f0; + ctx->total64 = 0; + ctx->process_block = sha1_process_block64; +} + +static const uint32_t init256[] = { + 0x6a09e667, + 0xbb67ae85, + 0x3c6ef372, + 0xa54ff53a, + 0x510e527f, + 0x9b05688c, + 0x1f83d9ab, + 0x5be0cd19 +}; +static const uint32_t init512_lo[] = { + 0xf3bcc908, + 0x84caa73b, + 0xfe94f82b, + 0x5f1d36f1, + 0xade682d1, + 0x2b3e6c1f, + 0xfb41bd6b, + 0x137e2179 +}; + +/* Used also for sha256 */ +void sha1_hash(const void *buffer, size_t len, sha1_ctx_t *ctx) +{ + unsigned in_buf = ctx->total64 & 63; + unsigned add = 64 - in_buf; + + ctx->total64 += len; + + while (len >= add) { /* transfer whole blocks while possible */ + memcpy(ctx->wbuffer + in_buf, buffer, add); + buffer = (const char *)buffer + add; + len -= add; + add = 64; + in_buf = 0; + ctx->process_block(ctx); + } + + memcpy(ctx->wbuffer + in_buf, buffer, len); +} + +/* Used also for sha256 */ +void sha1_end(void *resbuf, sha1_ctx_t *ctx) +{ + unsigned pad, in_buf; + + in_buf = ctx->total64 & 63; + /* Pad the buffer to the next 64-byte boundary with 0x80,0,0,0... */ + ctx->wbuffer[in_buf++] = 0x80; + + /* This loop iterates either once or twice, no more, no less */ + while (1) { + pad = 64 - in_buf; + memset(ctx->wbuffer + in_buf, 0, pad); + in_buf = 0; + /* Do we have enough space for the length count? */ + if (pad >= 8) { + /* Store the 64-bit counter of bits in the buffer in BE format */ + uint64_t t = ctx->total64 << 3; + t = hton64(t); + /* wbuffer is suitably aligned for this */ + *(uint64_t *) (&ctx->wbuffer[64 - 8]) = t; + } + ctx->process_block(ctx); + if (pad >= 8) + break; + } + + in_buf = (ctx->process_block == sha1_process_block64) ? 5 : 8; + /* This way we do not impose alignment constraints on resbuf: */ + if (SHA1_LITTLE_ENDIAN) { + unsigned i; + for (i = 0; i < in_buf; ++i) + ctx->hash[i] = htonl(ctx->hash[i]); + } + memcpy(resbuf, ctx->hash, sizeof(ctx->hash[0]) * in_buf); +} diff --git a/lib/plugins/CCpp_sha1.h b/lib/plugins/CCpp_sha1.h new file mode 100644 index 00000000..abadfd92 --- /dev/null +++ b/lib/plugins/CCpp_sha1.h @@ -0,0 +1,32 @@ +/* vi: set sw=4 ts=4: */ +/* + * Based on shasum from http://www.netsw.org/crypto/hash/ + * Majorly hacked up to use Dr Brian Gladman's sha1 code + * + * Copyright (C) 2002 Dr Brian Gladman , Worcester, UK. + * Copyright (C) 2003 Glenn L. McGrath + * Copyright (C) 2003 Erik Andersen + * + * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * + * --------------------------------------------------------------------------- + * Issue Date: 10/11/2002 + * + * This is a byte oriented version of SHA1 that operates on arrays of bytes + * stored in memory. It runs at 22 cycles per byte on a Pentium P4 processor + * + * --------------------------------------------------------------------------- + */ + +#define SHA1_RESULT_LEN (5 * 4) + +typedef struct sha1_ctx_t { + uint32_t hash[8]; /* 5, +3 elements for sha256 */ + uint64_t total64; + uint8_t wbuffer[64]; /* NB: always correctly aligned for uint64_t */ + void (*process_block)(struct sha1_ctx_t*); +} sha1_ctx_t; + +void sha1_begin(sha1_ctx_t *ctx); +void sha1_hash(const void *buffer, size_t len, sha1_ctx_t *ctx); +void sha1_end(void *resbuf, sha1_ctx_t *ctx); diff --git a/lib/plugins/FileTransfer.conf b/lib/plugins/FileTransfer.conf new file mode 100644 index 00000000..111c1c4b --- /dev/null +++ b/lib/plugins/FileTransfer.conf @@ -0,0 +1,35 @@ +# Configuration of the FileTransfer reporter plugin. +Enabled = yes + +# The plugin is invoked in the abrt.conf file, usually in the +# ActionsAndReporters option and/or the [cron] section. +# There are two modes of invocation: +# +# * Specify FileTransfer(one) in ActionsAndReporters directive. +# Immediately after crash is detected, the plugin transfers +# crash data to the server specified via URL directive in this file. +# +# * Specify FileTransfer(store) in ActionsAndReporters directive +# and add "HH:MM = FileTransfer" line in [cron] section. +# At the time of the crash, the plugin stores a record of it +# in its internal list. When specified time is reached, +# the plugin iterates through its internal list and sends +# every recorded crash to the specified URL. +# After that, the internal list is cleared. + + +# URL to upload the files to +# supported: ftp, ftps, http, https, scp, sftp, tftp, file +# for example: ftp://user:password@server.name/directory +# or: scp://user:password@server.name:port/directory etc. +# for testing: file:///tmp/test_directory +URL = + +# Archive type, one of .zip, .tar.gz or .tar.bz2 +ArchiveType = .tar.gz + +# How many times we try to upload the file +RetryCount = 3 + +# How long we wait between we retry the upload (in seconds) +RetryDelay = 20 diff --git a/lib/plugins/FileTransfer.cpp b/lib/plugins/FileTransfer.cpp new file mode 100644 index 00000000..cf7cf1f3 --- /dev/null +++ b/lib/plugins/FileTransfer.cpp @@ -0,0 +1,362 @@ +/* + FileTransfer.cpp + + Copyright (C) 2009 Daniel Novotny (dnovotny@redhat.com) + Copyright (C) 2009 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 _GNU_SOURCE +# define _GNU_SOURCE +#endif +#include +#include +#include +#include "abrtlib.h" +#include "abrt_curl.h" +#include "FileTransfer.h" +#include "debug_dump.h" +#include "abrt_exception.h" +#include "comm_layer_inner.h" + +using namespace std; + +#define HBLEN 255 +#define FILETRANSFER_DIRLIST DEBUG_DUMPS_DIR "/FileTransferDirlist.txt" + +CFileTransfer::CFileTransfer() +: + m_sArchiveType(".tar.gz"), + m_nRetryCount(3), + m_nRetryDelay(20) +{ +} + +void CFileTransfer::SendFile(const char *pURL, const char *pFilename) +{ + int len = strlen(pURL); + if (len == 0) + { + error_msg(_("FileTransfer: URL not specified")); + return; + } + + update_client(_("Sending archive %s to %s"), pFilename, pURL); + + string wholeURL = concat_path_file(pURL, strrchr(pFilename, '/') ? : pFilename); + + int count = m_nRetryCount; + while (1) + { + FILE *f = fopen(pFilename, "r"); + if (!f) + { + throw CABRTException(EXCEP_PLUGIN, "Can't open archive file '%s'", pFilename); + } + + struct stat buf; + fstat(fileno(f), &buf); /* never fails */ + + CURL *curl = xcurl_easy_init(); + /* enable uploading */ + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); + /* specify target */ + curl_easy_setopt(curl, CURLOPT_URL, wholeURL.c_str()); + /* FILE handle: passed to the default callback, it will fread() it */ + curl_easy_setopt(curl, CURLOPT_READDATA, f); + curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)buf.st_size); + + /* everything is done here; result 0 means success */ + int result = curl_easy_perform(curl); + + curl_easy_cleanup(curl); + fclose(f); + if (result == 0 || --count <= 0) + break; + /* retry the upload if not succesful, wait a bit before next try */ + sleep(m_nRetryDelay); + } +} + +static void create_tar(const char *archive_name, const char *directory) +{ + TAR *tar; + + if (tar_open(&tar, (char *)archive_name, NULL, O_WRONLY | O_CREAT, 0644, TAR_GNU) != 0) + { + return; + } + tar_append_tree(tar, (char *)directory, (char*)"."); + tar_close(tar); +} + +static void create_targz(const char *archive_name, const char *directory) +{ + char *name_without_gz = xstrdup(archive_name); + strrchr(name_without_gz, '.')[0] = '\0'; + + create_tar(name_without_gz, directory); + + int fd = open(name_without_gz, O_RDONLY); + if (fd < 0) + { + remove(name_without_gz); + free(name_without_gz); + return; + } + + gzFile gz = gzopen(archive_name, "w"); + if (gz == NULL) + { + close(fd); + remove(name_without_gz); + free(name_without_gz); + return; + } + + char buf[BUFSIZ]; + ssize_t bytesRead; + while ((bytesRead = full_read(fd, buf, BUFSIZ)) > 0) + { + gzwrite(gz, buf, bytesRead); // TODO: check that return value == bytesRead + } + + gzclose(gz); + close(fd); + remove(name_without_gz); + free(name_without_gz); +} + +static void create_tarbz2(const char * archive_name, const char * directory) +{ + char *name_without_bz2 = xstrdup(archive_name); + strrchr(name_without_bz2, '.')[0] = '\0'; + + create_tar(name_without_bz2, directory); + + int tarFD = open(name_without_bz2, O_RDONLY); + if (tarFD == -1) + { + remove(name_without_bz2); + free(name_without_bz2); + return; + } + FILE *f = fopen(archive_name, "w"); + if (f == NULL) + { + close(tarFD); + remove(name_without_bz2); + free(name_without_bz2); + return; + } + int bzError; + BZFILE *bz = BZ2_bzWriteOpen(&bzError, f, /*BLOCK_MULTIPLIER:*/ 7, 0, 0); + if (bz == NULL) + { + fclose(f); + close(tarFD); + remove(name_without_bz2); + free(name_without_bz2); + return; + } + + char buf[BUFSIZ]; + ssize_t bytesRead; + while ((bytesRead = read(tarFD, buf, BUFSIZ)) > 0) + { + BZ2_bzWrite(&bzError, bz, buf, bytesRead); + } + + BZ2_bzWriteClose(&bzError, bz, 0, NULL, NULL); + fclose(f); + close(tarFD); + remove(name_without_bz2); + free(name_without_bz2); +} + +void CFileTransfer::CreateArchive(const char *pArchiveName, const char *pDir) +{ + if (m_sArchiveType == ".tar") + { + create_tar(pArchiveName, pDir); + } + else if (m_sArchiveType == ".tar.gz") + { + create_targz(pArchiveName, pDir); + } + else if (m_sArchiveType == ".tar.bz2") + { + create_tarbz2(pArchiveName, pDir); + } + else + { + throw CABRTException(EXCEP_PLUGIN, "Unknown/unsupported archive type %s", m_sArchiveType.c_str()); + } +} + +/* Returns the last component of the directory path. + * Careful to not return "" on "/path/path2/", but "path2". + */ +static string DirBase(const char *pStr) +{ + int end = strlen(pStr); + if (end > 1 && pStr[end-1] == '/') + { + end--; + } + int beg = end; + while (beg > 0 && pStr[beg-1] != '/') + { + beg--; + } + return string(pStr + beg, end - beg); +} + +void CFileTransfer::Run(const char *pActionDir, const char *pArgs, int force) +{ + if (strcmp(pArgs, "store") == 0) + { + /* Remember pActiveDir for later sending */ + FILE *dirlist = fopen(FILETRANSFER_DIRLIST, "a"); + fprintf(dirlist, "%s\n", pActionDir); + fclose(dirlist); + VERB3 log("Remembered '%s' for future file transfer", pActionDir); + return; + } + + update_client(_("FileTransfer: Creating a report...")); + + char hostname[HBLEN]; + gethostname(hostname, HBLEN-1); + hostname[HBLEN-1] = '\0'; + + char tmpdir_name[] = "/tmp/abrtuploadXXXXXX"; + /* mkdtemp does mkdir(xxx, 0700), should be safe (is it?) */ + if (mkdtemp(tmpdir_name) == NULL) + { + throw CABRTException(EXCEP_PLUGIN, "Can't mkdir a temporary directory in /tmp"); + } + + if (strcmp(pArgs, "one") == 0) + { + /* Just send one archive */ + string archivename = ssprintf("%s/%s-%s%s", tmpdir_name, hostname, DirBase(pActionDir).c_str(), m_sArchiveType.c_str()); + try + { + CreateArchive(archivename.c_str(), pActionDir); + SendFile(m_sURL.c_str(), archivename.c_str()); + } + catch (CABRTException& e) + { + error_msg(_("Cannot create and send an archive: %s"), e.what()); + } + unlink(archivename.c_str()); + } + else + { + /* Tar up and send all remebered directories */ + FILE *dirlist = fopen(FILETRANSFER_DIRLIST, "r"); + if (!dirlist) + { + /* not an error */ + VERB3 log("No saved crashes to transfer"); + goto del_tmp_dir; + } + + char dirname[PATH_MAX]; + while (fgets(dirname, sizeof(dirname), dirlist) != NULL) + { + strchrnul(dirname, '\n')[0] = '\0'; + string archivename = ssprintf("%s/%s-%s%s", tmpdir_name, hostname, DirBase(dirname).c_str(), m_sArchiveType.c_str()); + try + { + VERB3 log("Creating archive '%s' of dir '%s'", archivename.c_str(), dirname); + CreateArchive(archivename.c_str(), dirname); + VERB3 log("Sending archive to '%s'", m_sURL.c_str()); + SendFile(m_sURL.c_str(), archivename.c_str()); + } + catch (CABRTException& e) + { + error_msg(_("Cannot create and send an archive: %s"), e.what()); + } + VERB3 log("Deleting archive '%s'", archivename.c_str()); + unlink(archivename.c_str()); + } + + fclose(dirlist); + /* all the files we're able to send should be sent now, + starting over with clean table */ + unlink(FILETRANSFER_DIRLIST); + } + + del_tmp_dir: + rmdir(tmpdir_name); +} + +void CFileTransfer::SetSettings(const map_plugin_settings_t& pSettings) +{ + m_pSettings = pSettings; + + map_plugin_settings_t::const_iterator end = pSettings.end(); + map_plugin_settings_t::const_iterator it; + it = pSettings.find("URL"); + if (it != end) + { + m_sURL = it->second; + } + + it = pSettings.find("RetryCount"); + if (it != end) + { + m_nRetryCount = xatoi_u(it->second.c_str()); + } + + it = pSettings.find("RetryDelay"); + if (it != end) + { + m_nRetryDelay = xatoi_u(it->second.c_str()); + } + + it = pSettings.find("ArchiveType"); + if (it != end) + { + /* currently supporting .tar, .tar.gz, .tar.bz2 and .zip */ + m_sArchiveType = it->second; + if (m_sArchiveType[0] != '.') + { + m_sArchiveType = "." + m_sArchiveType; + } + } +} + +//ok to delete? +//const map_plugin_settings_t& CFileTransfer::GetSettings() +//{ +// m_pSettings["URL"] = m_sURL; +// m_pSettings["RetryCount"] = to_string(m_nRetryCount); +// m_pSettings["RetryDelay"] = to_string(m_nRetryDelay); +// m_pSettings["ArchiveType"] = m_sArchiveType; +// +// return m_pSettings; +//} + +PLUGIN_INFO(ACTION, + CFileTransfer, + "FileTransfer", + "0.0.6", + _("Sends a report via FTP or SCTP"), + "dnovotny@redhat.com", + "https://fedorahosted.org/abrt/wiki", + ""); diff --git a/lib/plugins/FileTransfer.h b/lib/plugins/FileTransfer.h new file mode 100644 index 00000000..5e9f4938 --- /dev/null +++ b/lib/plugins/FileTransfer.h @@ -0,0 +1,48 @@ +/* + FileTransfer.h - header file for the file transfer plugin + - it uploads the file via ftp or sctp + + Copyright (C) 2009 Daniel Novotny (dnovotny@redhat.com) + Copyright (C) 2009 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 FILETRANSFER_H_ +#define FILETRANSFER_H_ + +#include +#include "plugin.h" +#include "action.h" + +class CFileTransfer : public CAction +{ + private: + std::string m_sURL; + std::string m_sArchiveType; + int m_nRetryCount; + int m_nRetryDelay; + + void CreateArchive(const char *pArchiveName, const char *pDir); + void SendFile(const char *pURL, const char *pFilename); + + public: + CFileTransfer(); + virtual void SetSettings(const map_plugin_settings_t& pSettings); +//ok to delete? +// virtual const map_plugin_settings_t& GetSettings(); + virtual void Run(const char *pActionDir, const char *pArgs, int force); +}; + +#endif /* FILETRANSFER_H_ */ diff --git a/lib/plugins/Kerneloops.conf b/lib/plugins/Kerneloops.conf new file mode 100644 index 00000000..77fca513 --- /dev/null +++ b/lib/plugins/Kerneloops.conf @@ -0,0 +1,19 @@ +Enabled = yes + +# Do we want kernel oopses to be visible to any user? +# Set to "yes" for compatibility with kerneloops.org tool. +InformAllUsers = yes + +# Automatically perform reporting. +# With default abrt.conf, it invokes KerneloopsReporter +# and thus reports oops to kerneloops.org. +# ("root" because all oopses are filed by abrt with user "root") +# AutoReportUIDs = root + +# Kerneloops Scanner configuration +################################## +SysLogFile = /var/log/messages + +# KerneloopsReporter configuration +################################## +SubmitURL = http://submit.kerneloops.org/submitoops.php diff --git a/lib/plugins/Kerneloops.cpp b/lib/plugins/Kerneloops.cpp new file mode 100644 index 00000000..1a2532e8 --- /dev/null +++ b/lib/plugins/Kerneloops.cpp @@ -0,0 +1,145 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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. + + Authors: + Anton Arapov + Arjan van de Ven + */ + +#include "abrtlib.h" +#include "Kerneloops.h" +#include "debug_dump.h" +#include "abrt_exception.h" +#include "comm_layer_inner.h" + +static unsigned hash_oops_str(const char *oops_ptr) +{ + unsigned char old_c; + unsigned char c = 0; + unsigned hash = 0; + + /* Special-case: if the first line is of form: + * WARNING: at net/wireless/core.c:614 wdev_cleanup_work+0xe9/0x120 [cfg80211]() (Not tainted) + * then hash only "file:line func+ofs/len" part. + */ + if (strncmp(oops_ptr, "WARNING: at ", sizeof("WARNING: at ")-1) == 0) { + const char *p = oops_ptr + sizeof("WARNING: at ")-1; + p = strchr(p, ' '); /* skip filename:NNN */ + if (p) { + p = strchrnul(p + 1, ' '); /* skip function_name+0xNN/0xNNN */ + oops_ptr += sizeof("WARNING: at ")-1; + while (oops_ptr < p) { + c = *oops_ptr++; + hash = ((hash << 5) ^ (hash >> 27)) ^ c; + } + return hash; + } + } + + while (1) { + old_c = c; + c = *oops_ptr++; + if (!c) + break; + if (c == '\n') { + // Exclude some lines which have process name - in some oops classes + // process name is irrelevant and changes with every oops. + // Lines we filter out: + // Pid: 8003, comm: Xorg Not tainted (2.6.27.9-159.fc10.i686 #1) + // Process Xorg (pid: 8003, ti=f0a0c000 task=f2380000 task.ti=f0a0c000) + if (strncmp(oops_ptr, "Pid: ", 5) == 0 + || strncmp(oops_ptr, "Process ", 8) == 0 + ) { + while (*oops_ptr && *oops_ptr != '\n') + oops_ptr++; + continue; + } + } + if (!isalnum(old_c)) { + if (c >= '0' && c <= '9') { + // Convert all (possibly hex) numbers to just one '0' + if (c == '0' && *oops_ptr == 'x') // "0xSOMETHING" + oops_ptr++; + while (isxdigit(*oops_ptr)) + oops_ptr++; + c = '0'; + } else + if ((c|0x20) >= 'a' && (c|0x20) <= 'f') { + // This *may be* a hex number without 0x prefix: "f0a0c000" + // Check that it indeed is, and replace with '0' + const char *oops_ptr2 = oops_ptr; + while (isxdigit(*oops_ptr2)) + oops_ptr2++; + // Does it end in a letter which is not a hex digit? + // (Example: "abcw" is not a hex number, "abc " is) + if (!isalpha(*oops_ptr2)) { + // It's "abc " case. Skip the "abc" string + oops_ptr = oops_ptr2; + c = '0'; + } + // else: hash the string as-is + } + } + // TODO: Drop call trace tail - in interrupt-driven oopses, + // everything before interrupt is irrelevant. + // Example of call trace part of oops: + // Call Trace: + // [] ? radeon_cp_resume+0x7d/0xbc [radeon] + // [] ? drm_ioctl+0x1b0/0x225 [drm] + // [] ? radeon_cp_resume+0x0/0xbc [radeon] + // [] ? vfs_ioctl+0x50/0x69 + // [] ? do_vfs_ioctl+0x23b/0x247 + // [] ? audit_syscall_entry+0xf9/0x123 + // [] ? sys_ioctl+0x40/0x5c + // [] ? syscall_call+0x7/0xb + + /* An algorithm proposed by Donald E. Knuth in The Art Of Computer + * Programming Volume 3, under the topic of sorting and search + * chapter 6.4. + */ + hash = ((hash << 5) ^ (hash >> 27)) ^ c; + } + return hash; +} + +std::string CAnalyzerKerneloops::GetLocalUUID(const char *pDebugDumpDir) +{ + VERB3 log("Getting local universal unique identification"); + + std::string oops; + { + CDebugDump dd; + dd.Open(pDebugDumpDir); + dd.LoadText(FILENAME_BACKTRACE, oops); + } + + unsigned hash = hash_oops_str(oops.c_str()); + hash &= 0x7FFFFFFF; + + return to_string(hash); +} + +std::string CAnalyzerKerneloops::GetGlobalUUID(const char *pDebugDumpDir) +{ + return GetLocalUUID(pDebugDumpDir); +} + +PLUGIN_INFO(ANALYZER, + CAnalyzerKerneloops, + "Kerneloops", + "0.0.2", + _("Analyzes kernel oopses"), + "anton@redhat.com", + "https://people.redhat.com/aarapov", + ""); diff --git a/lib/plugins/Kerneloops.h b/lib/plugins/Kerneloops.h new file mode 100644 index 00000000..2160f444 --- /dev/null +++ b/lib/plugins/Kerneloops.h @@ -0,0 +1,42 @@ +/* + * Copyright 2007, Intel Corporation + * Copyright 2009, Red Hat Inc. + * + * This file is part of Abrt. + * + * This program file 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; version 2 of the License. + * + * 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 in a file named COPYING; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * Authors: + * Anton Arapov + * Arjan van de Ven + */ + +#ifndef __INCLUDE_GUARD_KERNELOOPS_H_ +#define __INCLUDE_GUARD_KERNELOOPS_H_ + +#include "plugin.h" +#include "analyzer.h" +#include + +class CAnalyzerKerneloops : public CAnalyzer +{ + public: + virtual std::string GetLocalUUID(const char *pDebugDumpDir); + virtual std::string GetGlobalUUID(const char *pDebugDumpDir); + virtual void CreateReport(const char *pDebugDumpDir, int force) {} +}; + +#endif diff --git a/lib/plugins/KerneloopsReporter.cpp b/lib/plugins/KerneloopsReporter.cpp new file mode 100644 index 00000000..2b4df3a0 --- /dev/null +++ b/lib/plugins/KerneloopsReporter.cpp @@ -0,0 +1,145 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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. + + Authors: + Anton Arapov + Arjan van de Ven + */ + +#include "abrtlib.h" +#include "abrt_curl.h" +#include "KerneloopsReporter.h" +#include "comm_layer_inner.h" +#include "abrt_exception.h" + +/* helpers */ +static size_t writefunction(void *ptr, size_t size, size_t nmemb, void *stream) +{ + size *= nmemb; +/* + char *c, *c1, *c2; + + log("received: '%*.*s'\n", (int)size, (int)size, (char*)ptr); + c = (char*)xzalloc(size + 1); + memcpy(c, ptr, size); + c1 = strstr(c, "201 "); + if (c1) { + c1 += 4; + c2 = strchr(c1, '\n'); + if (c2) + *c2 = 0; + } + free(c); +*/ + + return size; +} + +/* Send oops data to kerneloops.org-style site, using HTTP POST */ +/* Returns 0 on success */ +static CURLcode http_post_to_kerneloops_site(const char *url, const char *oopsdata) +{ + CURLcode ret; + CURL *handle; + struct curl_httppost *post = NULL; + struct curl_httppost *last = NULL; + + handle = xcurl_easy_init(); + curl_easy_setopt(handle, CURLOPT_URL, url); + + curl_formadd(&post, &last, + CURLFORM_COPYNAME, "oopsdata", + CURLFORM_COPYCONTENTS, oopsdata, + CURLFORM_END); + curl_formadd(&post, &last, + CURLFORM_COPYNAME, "pass_on_allowed", + CURLFORM_COPYCONTENTS, "yes", + CURLFORM_END); + + + curl_easy_setopt(handle, CURLOPT_HTTPPOST, post); + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, writefunction); + + ret = curl_easy_perform(handle); + + curl_formfree(post); + curl_easy_cleanup(handle); + + return ret; +} + + +/* class CKerneloopsReporter */ +CKerneloopsReporter::CKerneloopsReporter() : + m_sSubmitURL("http://submit.kerneloops.org/submitoops.php") +{} + +std::string CKerneloopsReporter::Report(const map_crash_data_t& pCrashData, + const map_plugin_settings_t& pSettings, + const char *pArgs) +{ + CURLcode ret; + + update_client(_("Creating and submitting a report...")); + + map_crash_data_t::const_iterator it = pCrashData.find(FILENAME_BACKTRACE); + if (it != pCrashData.end()) { + ret = http_post_to_kerneloops_site( + m_sSubmitURL.c_str(), + it->second[CD_CONTENT].c_str() + ); + } + + if (ret != CURLE_OK) { + char* err_str = xasprintf("Kernel oops has not been sent due to %s", curl_easy_strerror(ret)); + CABRTException e(EXCEP_PLUGIN, err_str); + free(err_str); + throw e; + } + /* Server replies with: + * 200 thank you for submitting the kernel oops information + * RemoteIP: 34192fd15e34bf60fac6a5f01bba04ddbd3f0558 + * - no URL or bug ID apparently... + */ + return "Kernel oops report was uploaded"; +} + +void CKerneloopsReporter::SetSettings(const map_plugin_settings_t& pSettings) +{ + m_pSettings = pSettings; + + map_plugin_settings_t::const_iterator end = pSettings.end(); + map_plugin_settings_t::const_iterator it; + it = pSettings.find("SubmitURL"); + if (it != end) { + m_sSubmitURL = it->second; + } +} + +//ok to delete? +//const map_plugin_settings_t& CKerneloopsReporter::GetSettings() +//{ +// m_pSettings["SubmitURL"] = m_sSubmitURL; +// +// return m_pSettings; +//} + +PLUGIN_INFO(REPORTER, + CKerneloopsReporter, + "KerneloopsReporter", + "0.0.1", + _("Sends kernel oops information to kerneloops.org"), + "anton@redhat.com", + "http://people.redhat.com/aarapov", + PLUGINS_LIB_DIR"/KerneloopsReporter.glade"); diff --git a/lib/plugins/KerneloopsReporter.glade b/lib/plugins/KerneloopsReporter.glade new file mode 100644 index 00000000..1ba287b8 --- /dev/null +++ b/lib/plugins/KerneloopsReporter.glade @@ -0,0 +1,118 @@ + + + + + + 12 + False + True + abrt + normal + False + + + True + vertical + 12 + + + True + 0 + none + + + True + 6 + 12 + + + True + 2 + 12 + 6 + + + True + 0 + Submit URL: + + + GTK_FILL + + + + + True + True + + + + 1 + 2 + + + + + + + + + True + <b>Kerneloops Reporter plugin configuration</b> + True + + + + + False + False + 1 + + + + + True + end + + + gtk-cancel + True + True + True + True + + + False + False + 0 + + + + + gtk-apply + True + True + True + True + + + False + False + 1 + + + + + False + end + 0 + + + + + + button2 + button1 + + + diff --git a/lib/plugins/KerneloopsReporter.h b/lib/plugins/KerneloopsReporter.h new file mode 100644 index 00000000..cb939d0d --- /dev/null +++ b/lib/plugins/KerneloopsReporter.h @@ -0,0 +1,51 @@ +/* + * Copyright 2007, Intel Corporation + * Copyright 2009, Red Hat Inc. + * + * This file is part of Abrt. + * + * This program file 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; version 2 of the License. + * + * 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 in a file named COPYING; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * Authors: + * Anton Arapov + * Arjan van de Ven + */ + +#ifndef __INCLUDE_GUARD_KERNELOOPSREPORTER_H_ +#define __INCLUDE_GUARD_KERNELOOPSREPORTER_H_ + +#include "plugin.h" +#include "reporter.h" + +#include + +class CKerneloopsReporter : public CReporter +{ + private: + std::string m_sSubmitURL; + + public: + CKerneloopsReporter(); + + virtual void SetSettings(const map_plugin_settings_t& pSettings); +//ok to delete? +// virtual const map_plugin_settings_t& GetSettings(); + virtual std::string Report(const map_crash_data_t& pCrashData, + const map_plugin_settings_t& pSettings, + const char *pArgs); +}; + +#endif diff --git a/lib/plugins/KerneloopsScanner.cpp b/lib/plugins/KerneloopsScanner.cpp new file mode 100644 index 00000000..dc1f6d0a --- /dev/null +++ b/lib/plugins/KerneloopsScanner.cpp @@ -0,0 +1,203 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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. + + Authors: + Anton Arapov + Arjan van de Ven +*/ + +#include +#include +#include /* __NR_syslog */ +#include "abrtlib.h" +#include "debug_dump.h" +#include "abrt_exception.h" +#include "comm_layer_inner.h" +#include "KerneloopsSysLog.h" +#include "KerneloopsScanner.h" + +// TODO: https://fedorahosted.org/abrt/ticket/78 + +static int scan_dmesg(vector_string_t& oopsList) +{ + VERB1 log("Scanning dmesg"); + + /* syslog(3) - read the last len bytes from the log buffer + * (non-destructively), but dont read more than was written + * into the buffer since the last"clear ring buffer" cmd. + * Returns the number of bytes read. + */ + char *buffer = (char*)xzalloc(16*1024); + syscall(__NR_syslog, 3, buffer, 16*1024 - 1); /* always NUL terminated */ + int cnt_FoundOopses = extract_oopses(oopsList, buffer, strlen(buffer)); + free(buffer); + + return cnt_FoundOopses; +} + + +/* "dumpoops" tool uses these two functions too */ +extern "C" { + +int scan_syslog_file(vector_string_t& oopsList, const char *filename, time_t *last_changed_p) +{ + VERB1 log("Scanning syslog file '%s'", filename); + + char *buffer; + struct stat statb; + int fd; + int cnt_FoundOopses; + ssize_t sz; + fd = open(filename, O_RDONLY); + if (fd < 0) + return 0; + statb.st_size = 0; /* paranoia */ + if (fstat(fd, &statb) != 0 || statb.st_size < 1) + { + close(fd); + return 0; + } + + if (last_changed_p != NULL) + { + if (*last_changed_p == statb.st_mtime) + { + VERB1 log("Syslog file '%s' hasn't changed since last scan, skipping", filename); + close(fd); + return 0; + } + *last_changed_p = statb.st_mtime; + } + + /* + * In theory we have a race here, since someone could spew + * to /var/log/messages before we read it in... we try to + * deal with it by reading at most 10kbytes extra. If there's + * more than that.. any oops will be in dmesg anyway. + * Do not try to allocate an absurd amount of memory; ignore + * older log messages because they are unlikely to have + * sufficiently recent data to be useful. 32MB is more + * than enough; it's not worth looping through more log + * if the log is larger than that. + */ + sz = statb.st_size + 10*1024; + if (statb.st_size > (32*1024*1024 - 10*1024)) + { + xlseek(fd, statb.st_size - (32*1024*1024 - 10*1024), SEEK_SET); + sz = 32*1024*1024; + } + buffer = (char*)xzalloc(sz); + sz = full_read(fd, buffer, sz); + close(fd); + + cnt_FoundOopses = 0; + if (sz > 0) + cnt_FoundOopses = extract_oopses(oopsList, buffer, sz); + free(buffer); + + return cnt_FoundOopses; +} + +void save_oops_to_debug_dump(const vector_string_t& oopsList) +{ + unsigned countdown = 16; /* do not report hundreds of oopses */ + unsigned idx = oopsList.size(); + time_t t = time(NULL); + pid_t my_pid = getpid(); + + VERB1 log("Saving %u oopses as crash dump dirs", idx >= countdown ? countdown-1 : idx); + + while (idx != 0 && --countdown != 0) + { + char path[sizeof(DEBUG_DUMPS_DIR"/kerneloops-%lu-%lu-%lu") + 3 * sizeof(long)*3]; + sprintf(path, DEBUG_DUMPS_DIR"/kerneloops-%lu-%lu-%lu", (long)t, (long)my_pid, (long)idx); + try + { + std::string oops = oopsList.at(--idx); + const char *first_line = oops.c_str(); + char *second_line = (char*)strchr(first_line, '\n'); /* never NULL */ + *second_line++ = '\0'; + + CDebugDump dd; + dd.Create(path, /*uid:*/ 0); + dd.SaveText(FILENAME_ANALYZER, "Kerneloops"); + dd.SaveText(FILENAME_EXECUTABLE, "kernel"); + dd.SaveText(FILENAME_KERNEL, first_line); + dd.SaveText(FILENAME_CMDLINE, "not_applicable"); + dd.SaveText(FILENAME_BACKTRACE, second_line); + /* Optional, makes generated bz more informative */ + strchrnul(second_line, '\n')[0] = '\0'; + dd.SaveText(FILENAME_REASON, second_line); + } + catch (CABRTException& e) + { + throw CABRTException(EXCEP_PLUGIN, "%s: %s", __func__, e.what()); + } + } +} + +} /* extern "C" */ + + +CKerneloopsScanner::CKerneloopsScanner() +{ + int cnt_FoundOopses; + m_syslog_last_change = 0; + + /* Scan dmesg, on first call only */ + vector_string_t oopsList; + cnt_FoundOopses = scan_dmesg(oopsList); + if (cnt_FoundOopses > 0) + save_oops_to_debug_dump(oopsList); +} + +void CKerneloopsScanner::Run(const char *pActionDir, const char *pArgs, int force) +{ + const char *syslog_file = "/var/log/messages"; + map_plugin_settings_t::const_iterator it = m_pSettings.find("SysLogFile"); + if (it != m_pSettings.end()) + syslog_file = it->second.c_str(); + + vector_string_t oopsList; + int cnt_FoundOopses = scan_syslog_file(oopsList, syslog_file, &m_syslog_last_change); + if (cnt_FoundOopses > 0) + { + save_oops_to_debug_dump(oopsList); + /* + * This marker in syslog file prevents us from + * re-parsing old oopses (any oops before it is + * ignored by scan_syslog_file()). The only problem + * is that we can't be sure here that syslog_file + * is the file where syslog(xxx) stuff ends up. + */ + openlog("abrt", 0, LOG_KERN); + syslog(LOG_WARNING, + "Kerneloops: Reported %u kernel oopses to Abrt", + cnt_FoundOopses); + closelog(); + } +} + +PLUGIN_INFO(ACTION, + CKerneloopsScanner, + "KerneloopsScanner", + "0.0.1", + _("Periodically scans for and saves kernel oopses"), + "anton@redhat.com", + "http://people.redhat.com/aarapov", + ""); diff --git a/lib/plugins/KerneloopsScanner.h b/lib/plugins/KerneloopsScanner.h new file mode 100644 index 00000000..0904146e --- /dev/null +++ b/lib/plugins/KerneloopsScanner.h @@ -0,0 +1,42 @@ +/* + * Copyright 2007, Intel Corporation + * Copyright 2009, Red Hat Inc. + * + * This file is part of Abrt. + * + * This program file 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; version 2 of the License. + * + * 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 in a file named COPYING; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * Authors: + * Anton Arapov + * Arjan van de Ven + */ +#ifndef KERNELOOPSSCANNER_H_ +#define KERNELOOPSSCANNER_H_ + +#include "abrt_types.h" +#include "plugin.h" +#include "action.h" + +class CKerneloopsScanner : public CAction +{ +private: + time_t m_syslog_last_change; +public: + CKerneloopsScanner(); + virtual void Run(const char *pActionDir, const char *pArgs, int force); +}; + +#endif diff --git a/lib/plugins/KerneloopsSysLog.cpp b/lib/plugins/KerneloopsSysLog.cpp new file mode 100644 index 00000000..66cce416 --- /dev/null +++ b/lib/plugins/KerneloopsSysLog.cpp @@ -0,0 +1,366 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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. + + Authors: + Anton Arapov + Arjan van de Ven + */ + +#include "abrtlib.h" +#include "KerneloopsSysLog.h" +#include + +static void queue_oops(vector_string_t &vec, const char *data, const char *version) +{ + vec.push_back(ssprintf("%s\n%s", version, data)); +} + +/* + * extract_version tries to find the kernel version in given data + */ +static int extract_version(const char *linepointer, char *version) +{ + int ret; + + ret = 0; + if (strstr(linepointer, "Pid") + || strstr(linepointer, "comm") + || strstr(linepointer, "CPU") + || strstr(linepointer, "REGS") + || strstr(linepointer, "EFLAGS") + ) { + char* start; + char* end; + + start = strstr((char*)linepointer, "2.6."); + if (start) { + end = strchr(start, ')'); + if (!end) + end = strchrnul(start, ' '); + strncpy(version, start, end-start); + ret = 1; + } + } + + if (!ret) + strncpy(version, "undefined", 9); + + return ret; +} + +/* + * extract_oops tries to find oops signatures in a log + */ +struct line_info { + char *ptr; + char level; +}; +static int record_oops(vector_string_t &oopses, struct line_info* lines_info, int oopsstart, int oopsend) +{ + int q; + int len; + int is_version; + char *oops; + char *version; + + len = 2; + for (q = oopsstart; q <= oopsend; q++) + len += strlen(lines_info[q].ptr) + 1; + + oops = (char*)xzalloc(len); + version = (char*)xzalloc(len); + + is_version = 0; + for (q = oopsstart; q <= oopsend; q++) { + if (!is_version) + is_version = extract_version(lines_info[q].ptr, version); + if (lines_info[q].ptr[0]) { + strcat(oops, lines_info[q].ptr); + strcat(oops, "\n"); + } + } + int rv = 1; + /* too short oopses are invalid */ + if (strlen(oops) > 100) { + queue_oops(oopses, oops, version); + } else { + VERB3 log("Dropped oops: too short"); + rv = 0; + } + free(oops); + free(version); + return rv; +} +#define REALLOC_CHUNK 1000 +int extract_oopses(vector_string_t &oopses, char *buffer, size_t buflen) +{ + char *c; + int linecount = 0; + int lines_info_alloc = 0; + struct line_info *lines_info = NULL; + + /* Split buffer into lines */ + + if (buflen != 0) + buffer[buflen - 1] = '\n'; /* the buffer usually ends with \n, but let's make sure */ + c = buffer; + while (c < buffer + buflen) { + char linelevel; + char *c9; + char *colon; + + c9 = (char*)memchr(c, '\n', buffer + buflen - c); /* a \n will always be found */ + assert(c9); + *c9 = '\0'; /* turn the \n into a string termination */ + if (c9 == c) + goto next_line; + + /* Is it a syslog file (/var/log/messages or similar)? + * Even though _usually_ it looks like "Nov 19 12:34:38 localhost kernel: xxx", + * some users run syslog in non-C locale: + * "2010-02-22T09:24:08.156534-08:00 gnu-4 gnome-session[2048]: blah blah" + * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ !!! + * We detect it by checking for N:NN:NN pattern in first 15 chars + * (and this still is not good enough... false positive: "pci 0000:15:00.0: PME# disabled") + */ + colon = strchr(c, ':'); + if (colon && colon > c && colon < c + 15 + && isdigit(colon[-1]) /* N:... */ + && isdigit(colon[1]) /* ...N:NN:... */ + && isdigit(colon[2]) + && colon[3] == ':' + && isdigit(colon[4]) /* ...N:NN:NN... */ + && isdigit(colon[5]) + ) { + /* It's syslog file, not a bare dmesg */ + + /* Skip non-kernel lines */ + char *kernel_str = strstr(c, "kernel: "); + if (kernel_str == NULL) { + /* if we see our own marker: + * "hostname abrt: Kerneloops: Reported 1 kernel oopses to Abrt" + * we know we submitted everything upto here already */ + if (strstr(c, "abrt:") && strstr(c, "Abrt")) { + VERB3 log("Found our marker at line %d, restarting line count from 0", linecount); + linecount = 0; + lines_info_alloc = 0; + free(lines_info); + lines_info = NULL; + } + goto next_line; + } + c = kernel_str + sizeof("kernel: ")-1; + } + + linelevel = 0; + /* store and remove kernel log level */ + if (*c == '<' && c[1] && c[2] == '>') { + linelevel = c[1]; + c += 3; + } + /* remove jiffies time stamp counter if present */ + if (*c == '[') { + char *c2 = strchr(c, '.'); + char *c3 = strchr(c, ']'); + if (c2 && c3 && (c2 < c3) && (c3-c) < 14 && (c2-c) < 8) { + c = c3 + 1; + if (*c == ' ') + c++; + } + } + if (linecount >= lines_info_alloc) { + lines_info_alloc += REALLOC_CHUNK; + lines_info = (line_info*)xrealloc(lines_info, + lines_info_alloc * sizeof(struct line_info)); + } + lines_info[linecount].ptr = c; + lines_info[linecount].level = linelevel; + linecount++; +next_line: + c = c9 + 1; + } + + /* Analyze lines */ + + int i; + char prevlevel = 0; + int oopsstart = -1; + int inbacktrace = 0; + int oopsesfound = 0; + + i = 0; + while (i < linecount) { + char *curline = lines_info[i].ptr; + + if (curline == NULL) { + i++; + continue; + } + while (*curline == ' ') + curline++; + + if (oopsstart < 0) { + /* find start-of-oops markers */ + if (strstr(curline, "general protection fault:")) + oopsstart = i; + else if (strstr(curline, "BUG:")) + oopsstart = i; + else if (strstr(curline, "kernel BUG at")) + oopsstart = i; + else if (strstr(curline, "do_IRQ: stack overflow:")) + oopsstart = i; + else if (strstr(curline, "RTNL: assertion failed")) + oopsstart = i; + else if (strstr(curline, "Eeek! page_mapcount(page) went negative!")) + oopsstart = i; + else if (strstr(curline, "near stack overflow (cur:")) + oopsstart = i; + else if (strstr(curline, "double fault:")) + oopsstart = i; + else if (strstr(curline, "Badness at")) + oopsstart = i; + else if (strstr(curline, "NETDEV WATCHDOG")) + oopsstart = i; + else if (strstr(curline, "WARNING: at ")) /* WARN_ON() generated message */ + oopsstart = i; + else if (strstr(curline, "Unable to handle kernel")) + oopsstart = i; + else if (strstr(curline, "sysctl table check failed")) + oopsstart = i; + else if (strstr(curline, "INFO: possible recursive locking detected")) + oopsstart = i; + // Not needed: "--[ cut here ]--" is always followed + // by "Badness at", "kernel BUG at", or "WARNING: at" string + //else if (strstr(curline, "------------[ cut here ]------------")) + // oopsstart = i; + else if (strstr(curline, "list_del corruption.")) + oopsstart = i; + else if (strstr(curline, "list_add corruption.")) + oopsstart = i; + if (strstr(curline, "Oops:") && i >= 3) + oopsstart = i-3; + + if (oopsstart >= 0) { + /* debug information */ + VERB3 { + log("Found oops at line %d: '%s'", oopsstart, lines_info[oopsstart].ptr); + if (oopsstart != i) + log("Trigger line is %d: '%s'", i, c); + } + /* try to find the end marker */ + int i2 = i + 1; + while (i2 < linecount && i2 < (i+50)) { + if (strstr(lines_info[i2].ptr, "---[ end trace")) { + inbacktrace = 1; + i = i2; + break; + } + i2++; + } + } + } + + /* Are we entering a call trace part? */ + /* a call trace starts with "Call Trace:" or with the " [<.......>] function+0xFF/0xAA" pattern */ + if (oopsstart >= 0 && !inbacktrace) { + if (strstr(curline, "Call Trace:")) + inbacktrace = 1; + else + if (strnlen(curline, 9) > 8 + && curline[0] == '[' && curline[1] == '<' + && strstr(curline, ">]") + && strstr(curline, "+0x") + && strstr(curline, "/0x") + ) { + inbacktrace = 1; + } + } + + /* Are we at the end of an oops? */ + else if (oopsstart >= 0 && inbacktrace) { + int oopsend = INT_MAX; + + /* line needs to start with " [" or have "] [" if it is still a call trace */ + /* example: "[] radeon_get_ring_head+0x16/0x41 [radeon]" */ + if (curline[0] != '[' + && !strstr(curline, "] [") + && !strstr(curline, "--- Exception") + && !strstr(curline, "LR =") + && !strstr(curline, "<#DF>") + && !strstr(curline, "") + && !strstr(curline, "") + && !strstr(curline, "<>") + && strncmp(curline, "Code: ", 6) != 0 + && strncmp(curline, "RIP ", 4) != 0 + && strncmp(curline, "RSP ", 4) != 0 + ) { + oopsend = i-1; /* not a call trace line */ + } + /* oops lines are always more than 8 chars long */ + else if (strnlen(curline, 8) < 8) + oopsend = i-1; + /* single oopses are of the same loglevel */ + else if (lines_info[i].level != prevlevel) + oopsend = i-1; + else if (strstr(curline, "Instruction dump:")) + oopsend = i; + /* if a new oops starts, this one has ended */ + else if (strstr(curline, "WARNING: at ") && oopsstart != i) /* WARN_ON() generated message */ + oopsend = i-1; + else if (strstr(curline, "Unable to handle") && oopsstart != i) + oopsend = i-1; + /* kernel end-of-oops marker (not including marker itself) */ + else if (strstr(curline, "---[ end trace")) + oopsend = i-1; + + if (oopsend <= i) { + VERB3 log("End of oops at line %d (%d): '%s'", oopsend, i, lines_info[oopsend].ptr); + if (record_oops(oopses, lines_info, oopsstart, oopsend)) + oopsesfound++; + oopsstart = -1; + inbacktrace = 0; + } + } + + prevlevel = lines_info[i].level; + i++; + + if (oopsstart >= 0) { + /* Do we have a suspiciously long oops? Cancel it */ + if (i-oopsstart > 60) { + inbacktrace = 0; + oopsstart = -1; + VERB3 log("Dropped oops, too long"); + continue; + } + if (!inbacktrace && i-oopsstart > 40) { + /*inbacktrace = 0; - already is */ + oopsstart = -1; + VERB3 log("Dropped oops, too long"); + continue; + } + } + } /* while (i < linecount) */ + + /* process last oops if we have one */ + if (oopsstart >= 0 && inbacktrace) { + int oopsend = i-1; + VERB3 log("End of oops at line %d (end of file): '%s'", oopsend, lines_info[oopsend].ptr); + if (record_oops(oopses, lines_info, oopsstart, oopsend)) + oopsesfound++; + } + + free(lines_info); + return oopsesfound; +} diff --git a/lib/plugins/KerneloopsSysLog.h b/lib/plugins/KerneloopsSysLog.h new file mode 100644 index 00000000..a67b33d4 --- /dev/null +++ b/lib/plugins/KerneloopsSysLog.h @@ -0,0 +1,34 @@ +/* + * Copyright 2007, Intel Corporation + * Copyright 2009, Red Hat Inc. + * + * This file is part of Abrt. + * + * This program file 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; version 2 of the License. + * + * 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 in a file named COPYING; if not, write to the + * Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA + * + * Authors: + * Anton Arapov + * Arjan van de Ven + */ + +#ifndef __INCLUDE_GUARD_KERNELOOPSSYSLOG_H_ +#define __INCLUDE_GUARD_KERNELOOPSSYSLOG_H_ + +#include "abrt_types.h" + +int extract_oopses(vector_string_t &oopses, char *buffer, size_t buflen); + +#endif diff --git a/lib/plugins/Logger.conf b/lib/plugins/Logger.conf new file mode 100644 index 00000000..f96c5b80 --- /dev/null +++ b/lib/plugins/Logger.conf @@ -0,0 +1,6 @@ +# Configuration for Logger plugin +Enabled = yes + +LogPath = /var/log/abrt.log + +AppendLogs = yes diff --git a/lib/plugins/Logger.cpp b/lib/plugins/Logger.cpp new file mode 100644 index 00000000..ece450d4 --- /dev/null +++ b/lib/plugins/Logger.cpp @@ -0,0 +1,90 @@ +/* + Logger.cpp - it simple writes report to specific file + + Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) + Copyright (C) 2009 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 "abrtlib.h" +#include "Logger.h" +#include "debug_dump.h" +#include "comm_layer_inner.h" +#include "abrt_exception.h" + +CLogger::CLogger() : + m_sLogPath("/var/log/abrt.log"), + m_bAppendLogs(true) +{} + +void CLogger::SetSettings(const map_plugin_settings_t& pSettings) +{ + m_pSettings = pSettings; + + map_plugin_settings_t::const_iterator end = pSettings.end(); + map_plugin_settings_t::const_iterator it; + it = pSettings.find("LogPath"); + if (it != end) + { + m_sLogPath = it->second; + } + it = pSettings.find("AppendLogs"); + if (it != end) + { + m_bAppendLogs = string_to_bool(it->second.c_str()); + } +} + +//ok to delete? +//const map_plugin_settings_t& CLogger::GetSettings() +//{ +// m_pSettings["LogPath"] = m_sLogPath; +// m_pSettings["AppendLogs"] = m_bAppendLogs ? "yes" : "no"; +// +// return m_pSettings; +//} + +std::string CLogger::Report(const map_crash_data_t& pCrashData, + const map_plugin_settings_t& pSettings, + const char *pArgs) +{ + std::string description = make_description_logger(pCrashData); + + /* open, not fopen - want to set mode if we create the file, not just open */ + const char *fname = m_sLogPath.c_str(); + int fd = open(fname, + m_bAppendLogs ? O_WRONLY|O_CREAT|O_APPEND : O_WRONLY|O_CREAT|O_TRUNC, + 0600); + if (fd < 0) + throw CABRTException(EXCEP_PLUGIN, "Can't open '%s'", fname); + + update_client(_("Writing report to '%s'"), fname); + description += "\n\n\n"; + const char *desc = description.c_str(); + full_write(fd, desc, strlen(desc)); + close(fd); + + const char *format = m_bAppendLogs ? _("The report was appended to %s") : _("The report was stored to %s"); + return ssprintf(format, m_sLogPath.c_str()); +} + +PLUGIN_INFO(REPORTER, + CLogger, + "Logger", + "0.0.1", + _("Writes report to a file"), + "zprikryl@redhat.com", + "https://fedorahosted.org/abrt/wiki", + PLUGINS_LIB_DIR"/Logger.glade"); diff --git a/lib/plugins/Logger.glade b/lib/plugins/Logger.glade new file mode 100644 index 00000000..a0a909a4 --- /dev/null +++ b/lib/plugins/Logger.glade @@ -0,0 +1,135 @@ + + + + + + 12 + False + True + center-on-parent + abrt + normal + False + + + True + vertical + 12 + + + True + 0 + none + + + True + 6 + 12 + + + True + 2 + 2 + 12 + 6 + + + True + 0 + Logger file: + + + GTK_FILL + + + + + True + True + + + + 1 + 2 + + + + + Append new logs + True + True + False + 0 + True + + + 2 + 1 + 2 + + + + + + + + + True + <b>Logger plugin configuration</b> + True + + + + + False + False + 1 + + + + + True + end + + + gtk-cancel + True + True + True + True + + + False + False + 0 + + + + + gtk-apply + True + True + True + True + + + False + False + 1 + + + + + False + end + 0 + + + + + + button2 + button1 + + + diff --git a/lib/plugins/Logger.h b/lib/plugins/Logger.h new file mode 100644 index 00000000..aa7def32 --- /dev/null +++ b/lib/plugins/Logger.h @@ -0,0 +1,44 @@ +/* + Logger.h - header file for Logger reporter plugin + - it simply writes report to specific file + + Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) + Copyright (C) 2009 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 LOGGER_H_ +#define LOGGER_H_ + +#include "plugin.h" +#include "reporter.h" + +class CLogger : public CReporter +{ + private: + std::string m_sLogPath; + bool m_bAppendLogs; + public: + CLogger(); + + virtual void SetSettings(const map_plugin_settings_t& pSettings); +//ok to delete? +// virtual const map_plugin_settings_t& GetSettings(); + virtual std::string Report(const map_crash_data_t& pCrashData, + const map_plugin_settings_t& pSettings, + const char *pArgs); +}; + +#endif /* LOGGER_H_ */ diff --git a/lib/plugins/Mailx.conf b/lib/plugins/Mailx.conf new file mode 100644 index 00000000..ccd14292 --- /dev/null +++ b/lib/plugins/Mailx.conf @@ -0,0 +1,15 @@ +# Configuration to Email reporter plugin +Enabled = yes + +# In abrt.conf, plugin takes one parameter: subject (in "" if you need to embed spaces). +# If it isn't specified, then a default subject is taken from this file +Subject = "[abrt] crash report" + +# Your Email +EmailFrom = user@localhost + +# Email To +EmailTo = root@localhost + +# Warning! enabling this may cause sending a lot of MB via email +SendBinaryData = no diff --git a/lib/plugins/Mailx.cpp b/lib/plugins/Mailx.cpp new file mode 100644 index 00000000..a94a2c5c --- /dev/null +++ b/lib/plugins/Mailx.cpp @@ -0,0 +1,195 @@ +/* + Mailx.cpp + + Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) + Copyright (C) 2009 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 "abrtlib.h" +#include "Mailx.h" +#include "debug_dump.h" +#include "abrt_exception.h" +#include "comm_layer_inner.h" + +#define MAILX_COMMAND "/bin/mailx" + +CMailx::CMailx() : + m_sEmailFrom("user@localhost"), + m_sEmailTo("root@localhost"), + m_sSubject("[abrt] full crash report"), + m_bSendBinaryData(false) +{} + +static void exec_and_feed_input(uid_t uid, const char* pText, char **pArgs) +{ + int pipein[2]; + + pid_t child = fork_execv_on_steroids( + EXECFLG_INPUT | EXECFLG_QUIET | EXECFLG_SETGUID, + pArgs, + pipein, + /*unsetenv_vec:*/ NULL, + /*dir:*/ NULL, + uid); + + full_write(pipein[1], pText, strlen(pText)); + close(pipein[1]); + + waitpid(child, NULL, 0); /* wait for command completion */ +} + +static char** append_str_to_vector(char **vec, unsigned &size, const char *str) +{ + //log("old vec: %p", vec); + vec = (char**) xrealloc(vec, (size+2) * sizeof(vec[0])); + vec[size] = xstrdup(str); + //log("new vec: %p, added [%d] %p", vec, size, vec[size]); + size++; + vec[size] = NULL; + return vec; +} + +std::string CMailx::Report(const map_crash_data_t& pCrashData, + const map_plugin_settings_t& pSettings, + const char *pArgs) +{ + SetSettings(pSettings); + char **args = NULL; + unsigned arg_size = 0; + args = append_str_to_vector(args, arg_size, MAILX_COMMAND); + +//TODO: move email body generation to make_descr.cpp + std::string binaryFiles, commonFiles, additionalFiles, DUPHASHFile; + map_crash_data_t::const_iterator it; + for (it = pCrashData.begin(); it != pCrashData.end(); it++) + { + if (it->second[CD_TYPE] == CD_TXT) + { + if (it->first != CD_DUPHASH + && it->first != FILENAME_ARCHITECTURE + && it->first != FILENAME_KERNEL + && it->first != FILENAME_PACKAGE + ) { + additionalFiles += it->first; + additionalFiles += "\n-----\n"; + additionalFiles += it->second[CD_CONTENT]; + additionalFiles += "\n\n"; + } + else if (it->first == CD_DUPHASH) + { + DUPHASHFile += it->first; + DUPHASHFile += "\n-----\n"; + DUPHASHFile += it->second[CD_CONTENT]; + DUPHASHFile += "\n\n"; + } + else + { + commonFiles += it->first; + commonFiles += "\n-----\n"; + commonFiles += it->second[CD_CONTENT]; + commonFiles += "\n\n"; + } + } + if (it->second[CD_TYPE] == CD_BIN) + { + binaryFiles += " -a "; + binaryFiles += it->second[CD_CONTENT]; + if (m_bSendBinaryData) + { + args = append_str_to_vector(args, arg_size, "-a"); + args = append_str_to_vector(args, arg_size, it->second[CD_CONTENT].c_str()); + } + } + } + + std::string emailBody = "Duplicate check\n"; + emailBody += "=====\n\n"; + emailBody += DUPHASHFile; + emailBody += "\nCommon information\n"; + emailBody += "=====\n\n"; + emailBody += commonFiles; + emailBody += "\nAdditional information\n"; + emailBody += "=====\n\n"; + emailBody += additionalFiles; + emailBody += '\n'; + + args = append_str_to_vector(args, arg_size, "-s"); + args = append_str_to_vector(args, arg_size, (pArgs[0] != '\0' ? pArgs : m_sSubject.c_str())); + args = append_str_to_vector(args, arg_size, "-r"); + args = append_str_to_vector(args, arg_size, m_sEmailFrom.c_str()); + args = append_str_to_vector(args, arg_size, m_sEmailTo.c_str()); + + update_client(_("Sending an email...")); + const char *uid_str = get_crash_data_item_content(pCrashData, CD_UID).c_str(); + exec_and_feed_input(xatoi_u(uid_str), emailBody.c_str(), args); + + while (*args) + { + free(*args++); + } + args -= arg_size; + free(args); + + return "Email was sent to: " + m_sEmailTo; +} + +void CMailx::SetSettings(const map_plugin_settings_t& pSettings) +{ + m_pSettings = pSettings; + + map_plugin_settings_t::const_iterator end = pSettings.end(); + map_plugin_settings_t::const_iterator it; + it = pSettings.find("Subject"); + if (it != end) + { + m_sSubject = it->second; + } + it = pSettings.find("EmailFrom"); + if (it != end) + { + m_sEmailFrom = it->second; + } + it = pSettings.find("EmailTo"); + if (it != end) + { + m_sEmailTo = it->second; + } + it = pSettings.find("SendBinaryData"); + if (it != end) + { + m_bSendBinaryData = string_to_bool(it->second.c_str()); + } +} + +//ok to delete? +//const map_plugin_settings_t& CMailx::GetSettings() +//{ +// m_pSettings["Subject"] = m_sSubject; +// m_pSettings["EmailFrom"] = m_sEmailFrom; +// m_pSettings["EmailTo"] = m_sEmailTo; +// m_pSettings["SendBinaryData"] = m_bSendBinaryData ? "yes" : "no"; +// +// return m_pSettings; +//} + +PLUGIN_INFO(REPORTER, + CMailx, + "Mailx", + "0.0.2", + _("Sends an email with a report (via mailx command)"), + "zprikryl@redhat.com", + "https://fedorahosted.org/abrt/wiki", + PLUGINS_LIB_DIR"/Mailx.glade"); diff --git a/lib/plugins/Mailx.glade b/lib/plugins/Mailx.glade new file mode 100644 index 00000000..656204b5 --- /dev/null +++ b/lib/plugins/Mailx.glade @@ -0,0 +1,184 @@ + + + + + + 12 + False + True + center-on-parent + abrt + normal + False + + + True + vertical + 12 + + + True + 0 + none + + + True + 6 + 12 + + + True + 4 + 2 + 12 + 6 + + + True + 0 + Subject: + + + GTK_FILL + + + + + True + True + + + + 1 + 2 + + + + + True + True + + + + 1 + 2 + 1 + 2 + + + + + True + 0 + Your Email: + + + 1 + 2 + GTK_FILL + + + + + True + 0 + Recipient's Email: + + + 2 + 3 + GTK_FILL + + + + + True + True + + + + 1 + 2 + 2 + 3 + + + + + Send Binary Data + True + True + False + True + + + 2 + 3 + 4 + + + + + + + + + True + <b>Mailx plugin configuration</b> + True + + + + + False + False + 1 + + + + + True + end + + + gtk-cancel + True + True + True + True + + + False + False + 0 + + + + + gtk-apply + True + True + True + True + + + False + False + 1 + + + + + False + end + 0 + + + + + + button2 + button1 + + + diff --git a/lib/plugins/Mailx.h b/lib/plugins/Mailx.h new file mode 100644 index 00000000..aa870ec6 --- /dev/null +++ b/lib/plugins/Mailx.h @@ -0,0 +1,48 @@ +/* + Mailx.h - header file for Mailx reporter plugin + - it simple sends an email to specific address via mailx command + + Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) + Copyright (C) 2009 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 MAILX_H_ +#define MAILX_H_ + +#include +#include "plugin.h" +#include "reporter.h" + +class CMailx : public CReporter +{ + private: + std::string m_sEmailFrom; + std::string m_sEmailTo; + std::string m_sSubject; + bool m_bSendBinaryData; + + public: + CMailx(); + + virtual void SetSettings(const map_plugin_settings_t& pSettings); +//ok to delete? +// virtual const map_plugin_settings_t& GetSettings(); + virtual std::string Report(const map_crash_data_t& pCrashData, + const map_plugin_settings_t& pSettings, + const char *pArgs); +}; + +#endif diff --git a/lib/plugins/Makefile.am b/lib/plugins/Makefile.am new file mode 100644 index 00000000..147a778e --- /dev/null +++ b/lib/plugins/Makefile.am @@ -0,0 +1,151 @@ +AM_CPPFLAGS = -I$(srcdir)/../../inc -I$(srcdir)/../utils +pluginslibdir=$(PLUGINS_LIB_DIR) +pluginslib_LTLIBRARIES = \ + libCCpp.la \ + libMailx.la \ + libSQLite3.la \ + libLogger.la \ + libKerneloopsScanner.la\ + libKerneloops.la \ + libKerneloopsReporter.la \ + libRunApp.la \ + libSOSreport.la \ + libBugzilla.la \ + libRHTSupport.la \ + libReportUploader.la \ + libPython.la \ + libFileTransfer.la + +dist_pluginslib_DATA = \ + Logger.glade \ + Mailx.glade \ + Bugzilla.glade \ + RHTSupport.glade \ + ReportUploader.glade \ + KerneloopsReporter.glade + +pluginsconfdir = $(PLUGINS_CONF_DIR) +dist_pluginsconf_DATA = \ + CCpp.conf \ + Mailx.conf \ + SQLite3.conf \ + Logger.conf \ + Kerneloops.conf \ + Bugzilla.conf \ + RHTSupport.conf \ + ReportUploader.conf \ + FileTransfer.conf \ + Python.conf \ + SOSreport.conf + +man_MANS = \ + abrt-FileTransfer.7 \ + abrt-Bugzilla.7 \ + abrt-KerneloopsScanner.7 \ + abrt-KerneloopsReporter.7 \ + abrt-Logger.7 \ + abrt-Mailx.7 \ + abrt-plugins.7 \ + abrt-SQLite3.7 \ + abrt-RunApp.7 \ + abrt-ReportUploader.7 + +EXTRA_DIST = $(man_MANS) + +$(DESTDIR)/$(DEBUG_INFO_DIR): + $(mkdir_p) '$@' + +install-data-hook: $(DESTDIR)/$(DEBUG_INFO_DIR) + sed 's: = /var/: = $(localstatedir)/:g' -i \ + $(DESTDIR)$(sysconfdir)/abrt/plugins/SQLite3.conf \ + $(DESTDIR)$(sysconfdir)/abrt/plugins/Logger.conf + +INC_PATH=$(srcdir)/../../inc +UTILS_PATH=$(srcdir)/../utils + +# CCpp +libCCpp_la_SOURCES = CCpp.cpp CCpp.h CCpp_sha1.cpp CCpp_sha1.h +libCCpp_la_LDFLAGS = -avoid-version +#libCCpp_la_LIBADD = +libCCpp_la_CPPFLAGS = -I$(INC_PATH) -I$(UTILS_PATH) \ + -DCCPP_HOOK_PATH=\"${libexecdir}/abrt-hook-ccpp\" \ + -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ + -DLOCALSTATEDIR='"$(localstatedir)"' +# -DHOSTILE_KERNEL + +# Kerneloops +libKerneloops_la_SOURCES = Kerneloops.cpp Kerneloops.h +libKerneloops_la_LDFLAGS = -avoid-version +libKerneloops_la_CPPFLAGS = -I$(INC_PATH) -I$(UTILS_PATH) + +# KerneloopsReporter +libKerneloopsReporter_la_SOURCES = KerneloopsReporter.cpp KerneloopsReporter.h +libKerneloopsReporter_la_LDFLAGS = -avoid-version +libKerneloopsReporter_la_LIBADD = $(CURL_LIBS) +libKerneloopsReporter_la_CPPFLAGS = -I$(INC_PATH) -I$(UTILS_PATH) $(CURL_CFLAGS) -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" + +# KerneloopsScanner +libKerneloopsScanner_la_SOURCES = KerneloopsScanner.cpp KerneloopsScanner.h KerneloopsSysLog.cpp KerneloopsSysLog.h +libKerneloopsScanner_la_LDFLAGS = -avoid-version +libKerneloopsScanner_la_CPPFLAGS = -I$(INC_PATH) -I$(UTILS_PATH) -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" + +# Mailx +libMailx_la_SOURCES = Mailx.cpp Mailx.h +libMailx_la_LDFLAGS = -avoid-version +libMailx_la_CPPFLAGS = -I$(INC_PATH) -I$(UTILS_PATH) -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" -DLOCALSTATEDIR='"$(localstatedir)"' + +# SQLite3 +libSQLite3_la_SOURCES = SQLite3.cpp SQLite3.h +libSQLite3_la_LDFLAGS = -avoid-version +libSQLite3_la_LIBADD = $(SQLITE3_LIBS) +libSQLite3_la_CPPFLAGS = -I$(INC_PATH) -I$(UTILS_PATH) $(SQLITE3_CFLAGS) -DLOCALSTATEDIR='"$(localstatedir)"' + +# Logger +libLogger_la_SOURCES = Logger.cpp Logger.h +libLogger_la_LDFLAGS = -avoid-version +libLogger_la_CPPFLAGS = -I$(INC_PATH) -I$(UTILS_PATH) -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" + +# RunApp +libRunApp_la_SOURCES = RunApp.h RunApp.cpp +libRunApp_la_LDFLAGS = -avoid-version + +# SOSreport +libSOSreport_la_SOURCES = SOSreport.cpp SOSreport.h +libSOSreport_la_LDFLAGS = -avoid-version + +# Bugzilla +libBugzilla_la_SOURCES = Bugzilla.h Bugzilla.cpp +libBugzilla_la_LIBADD = $(XMLRPC_LIBS) $(XMLRPC_CLIENT_LIBS) +libBugzilla_la_LDFLAGS = -avoid-version +libBugzilla_la_CPPFLAGS = $(XMLRPC_CFLAGS) $(XMLRPC_CLIENT_CFLAGS) \ + -I$(INC_PATH) -I$(UTILS_PATH) \ + -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ + -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" + +# RHTSupport +libRHTSupport_la_SOURCES = RHTSupport.h RHTSupport.cpp +libRHTSupport_la_LIBADD = +libRHTSupport_la_LDFLAGS = -avoid-version -ltar +libRHTSupport_la_CPPFLAGS = \ + -I$(INC_PATH) -I$(UTILS_PATH) \ + -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ + -DPLUGINS_CONF_DIR=\"$(PLUGINS_CONF_DIR)\" \ + -DLOCALSTATEDIR='"$(localstatedir)"' + +# ReportUploader +libReportUploader_la_SOURCES = ReportUploader.h ReportUploader.cpp +libReportUploader_la_LDFLAGS = -avoid-version +libReportUploader_la_LIBADD = $(CURL_LIBS) +libReportUploader_la_CPPFLAGS = -I$(INC_PATH) -I$(UTILS_PATH) $(CURL_CFLAGS) -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" + +# Python +libPython_la_SOURCES = Python.h Python.cpp Python_hash.h Python_hash.cpp +#libPython_la_LIBADD = $(NSS_LIBS) +libPython_la_LDFLAGS = -avoid-version +libPython_la_CPPFLAGS = -I$(INC_PATH) -I$(UTILS_PATH) + +# FileTrasfer +libFileTransfer_la_SOURCES = FileTransfer.cpp FileTransfer.h +libFileTransfer_la_LDFLAGS = -avoid-version -ltar -lbz2 -lz +libFileTransfer_la_LIBADD = $(CURL_LIBS) +libFileTransfer_la_CPPFLAGS = -I$(INC_PATH) -I$(UTILS_PATH) $(CURL_CFLAGS) -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" diff --git a/lib/plugins/Python.conf b/lib/plugins/Python.conf new file mode 100644 index 00000000..3201c6da --- /dev/null +++ b/lib/plugins/Python.conf @@ -0,0 +1 @@ +Enabled = yes diff --git a/lib/plugins/Python.cpp b/lib/plugins/Python.cpp new file mode 100644 index 00000000..5f2f6e17 --- /dev/null +++ b/lib/plugins/Python.cpp @@ -0,0 +1,100 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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 "abrtlib.h" +#include "Python.h" +#include "debug_dump.h" +#include "abrt_exception.h" +#include "Python_hash.h" + +using namespace std; + +string CAnalyzerPython::GetLocalUUID(const char *pDebugDumpDir) +{ + CDebugDump dd; + dd.Open(pDebugDumpDir); + string bt; + dd.LoadText(FILENAME_BACKTRACE, bt); + + const char *bt_str = bt.c_str(); + const char *bt_end = strchrnul(bt_str, '\n'); + + char hash_str[MD5_RESULT_LEN*2 + 1]; + unsigned char hash2[MD5_RESULT_LEN]; + md5_ctx_t md5ctx; + md5_begin(&md5ctx); + // Better: + // "example.py:1::ZeroDivisionError: integer division or modulo by zero" + //md5_hash(bt_str, bt_end - bt_str, &md5ctx); + // For now using compat version: + { + char *copy = xstrndup(bt_str, bt_end - bt_str); + char *s = copy; + char *d = copy; + unsigned colon_cnt = 0; + while (*s && colon_cnt < 3) { + if (*s != ':') + *d++ = *s; + else + colon_cnt++; + s++; + } + // "example.py1" + md5_hash(copy, d - copy, &md5ctx); +//*d = '\0'; log("str:'%s'", copy); + free(copy); + } + md5_end(hash2, &md5ctx); + + // Hash is MD5_RESULT_LEN bytes long, but we use only first 4 + // (I don't know why old Python code was using only 4, I mimic that) + unsigned len = 4; + char *d = hash_str; + unsigned char *s = hash2; + while (len) { + *d++ = "0123456789abcdef"[*s >> 4]; + *d++ = "0123456789abcdef"[*s & 0xf]; + s++; + len--; + } + *d = '\0'; +//log("hash2:%s str:'%.*s'", hash_str, (int)(bt_end - bt_str), bt_str); + + return hash_str; +} +string CAnalyzerPython::GetGlobalUUID(const char *pDebugDumpDir) +{ + return GetLocalUUID(pDebugDumpDir); +} + +void CAnalyzerPython::Init() +{ +} + +void CAnalyzerPython::DeInit() +{ +} + +PLUGIN_INFO(ANALYZER, + CAnalyzerPython, + "Python", + "0.0.1", + _("Analyzes crashes in Python programs"), + "zprikryl@redhat.com, jmoskovc@redhat.com", + "https://fedorahosted.org/abrt/wiki", + ""); diff --git a/lib/plugins/Python.h b/lib/plugins/Python.h new file mode 100644 index 00000000..9f9395c5 --- /dev/null +++ b/lib/plugins/Python.h @@ -0,0 +1,36 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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 PYTHON_H_ +#define PYTHON_H_ + +#include +#include "plugin.h" +#include "analyzer.h" + +class CAnalyzerPython : public CAnalyzer +{ + public: + virtual std::string GetLocalUUID(const char *pDebugDumpDir); + virtual std::string GetGlobalUUID(const char *pDebugDumpDir); + virtual void CreateReport(const char *pDebugDumpDir, int force) {} + virtual void Init(); + virtual void DeInit(); +}; + +#endif /* PYTHON_H_ */ diff --git a/lib/plugins/Python_hash.cpp b/lib/plugins/Python_hash.cpp new file mode 100644 index 00000000..36ddacdc --- /dev/null +++ b/lib/plugins/Python_hash.cpp @@ -0,0 +1,445 @@ +/* vi: set sw=4 ts=4: */ +/* + * md5.c - Compute MD5 checksum of strings according to the + * definition of MD5 in RFC 1321 from April 1992. + * + * Written by Ulrich Drepper , 1995. + * + * Copyright (C) 1995-1999 Free Software Foundation, Inc. + * Copyright (C) 2001 Manuel Novoa III + * Copyright (C) 2003 Glenn L. McGrath + * Copyright (C) 2003 Erik Andersen + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ +#include "abrtlib.h" +#include "Python_hash.h" +#include + +#if defined(__BIG_ENDIAN__) && __BIG_ENDIAN__ +# define MD5_BIG_ENDIAN 1 +# define MD5_LITTLE_ENDIAN 0 +#elif __BYTE_ORDER == __BIG_ENDIAN +# define MD5_BIG_ENDIAN 1 +# define MD5_LITTLE_ENDIAN 0 +#elif __BYTE_ORDER == __LITTLE_ENDIAN +# define MD5_BIG_ENDIAN 0 +# define MD5_LITTLE_ENDIAN 1 +#else +# error "Can't determine endianness" +#endif + +/* SWAP_LEnn means "convert CPU<->little_endian if needed (by swapping bytes)" */ +#if MD5_BIG_ENDIAN +# define SWAP_BE32(x) (x) +# define SWAP_LE32(x) bswap_32(x) +#else +# define SWAP_BE32(x) bswap_32(x) +# define SWAP_LE32(x) (x) +#endif + + +/* 0: fastest, 3: smallest */ +#define MD5_SIZE_VS_SPEED 3 + +/* Initialize structure containing state of computation. + * (RFC 1321, 3.3: Step 3) + */ +void md5_begin(md5_ctx_t *ctx) +{ + ctx->A = 0x67452301; + ctx->B = 0xefcdab89; + ctx->C = 0x98badcfe; + ctx->D = 0x10325476; + ctx->total = 0; + ctx->buflen = 0; +} + +/* These are the four functions used in the four steps of the MD5 algorithm + * and defined in the RFC 1321. The first function is a little bit optimized + * (as found in Colin Plumbs public domain implementation). + * #define FF(b, c, d) ((b & c) | (~b & d)) + */ +#define FF(b, c, d) (d ^ (b & (c ^ d))) +#define FG(b, c, d) FF(d, b, c) +#define FH(b, c, d) (b ^ c ^ d) +#define FI(b, c, d) (c ^ (b | ~d)) + +#define rotl32(w, s) (((w) << (s)) | ((w) >> (32 - (s)))) + +/* Hash a single block, 64 bytes long and 4-byte aligned. */ +static void md5_hash_block(const void *buffer, md5_ctx_t *ctx) +{ + uint32_t correct_words[16]; + const uint32_t *words = (const uint32_t *)buffer; + +#if MD5_SIZE_VS_SPEED > 0 + static const uint32_t C_array[] = { + /* round 1 */ + 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, + 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, + 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, + 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, + /* round 2 */ + 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, + 0xd62f105d, 0x2441453, 0xd8a1e681, 0xe7d3fbc8, + 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, + 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, + /* round 3 */ + 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, + 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, + 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x4881d05, + 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, + /* round 4 */ + 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, + 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, + 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, + 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 + }; + static const char P_array[] = { +# if MD5_SIZE_VS_SPEED > 1 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, /* 1 */ +# endif + 1, 6, 11, 0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, /* 2 */ + 5, 8, 11, 14, 1, 4, 7, 10, 13, 0, 3, 6, 9, 12, 15, 2, /* 3 */ + 0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11, 2, 9 /* 4 */ + }; +# if MD5_SIZE_VS_SPEED > 1 + static const char S_array[] = { + 7, 12, 17, 22, + 5, 9, 14, 20, + 4, 11, 16, 23, + 6, 10, 15, 21 + }; +# endif /* MD5_SIZE_VS_SPEED > 1 */ +#endif + uint32_t A = ctx->A; + uint32_t B = ctx->B; + uint32_t C = ctx->C; + uint32_t D = ctx->D; + + /* Process all bytes in the buffer with 64 bytes in each round of + the loop. */ + uint32_t *cwp = correct_words; + uint32_t A_save = A; + uint32_t B_save = B; + uint32_t C_save = C; + uint32_t D_save = D; + +#if MD5_SIZE_VS_SPEED > 1 + const uint32_t *pc; + const char *pp; + const char *ps; + int i; + uint32_t temp; + + for (i = 0; i < 16; i++) { + cwp[i] = SWAP_LE32(words[i]); + } + words += 16; + +# if MD5_SIZE_VS_SPEED > 2 + pc = C_array; + pp = P_array; + ps = S_array - 4; + + for (i = 0; i < 64; i++) { + if ((i & 0x0f) == 0) + ps += 4; + temp = A; + switch (i >> 4) { + case 0: + temp += FF(B, C, D); + break; + case 1: + temp += FG(B, C, D); + break; + case 2: + temp += FH(B, C, D); + break; + case 3: + temp += FI(B, C, D); + } + temp += cwp[(int) (*pp++)] + *pc++; + temp = rotl32(temp, ps[i & 3]); + temp += B; + A = D; + D = C; + C = B; + B = temp; + } +# else + pc = C_array; + pp = P_array; + ps = S_array; + + for (i = 0; i < 16; i++) { + temp = A + FF(B, C, D) + cwp[(int) (*pp++)] + *pc++; + temp = rotl32(temp, ps[i & 3]); + temp += B; + A = D; + D = C; + C = B; + B = temp; + } + ps += 4; + for (i = 0; i < 16; i++) { + temp = A + FG(B, C, D) + cwp[(int) (*pp++)] + *pc++; + temp = rotl32(temp, ps[i & 3]); + temp += B; + A = D; + D = C; + C = B; + B = temp; + } + ps += 4; + for (i = 0; i < 16; i++) { + temp = A + FH(B, C, D) + cwp[(int) (*pp++)] + *pc++; + temp = rotl32(temp, ps[i & 3]); + temp += B; + A = D; + D = C; + C = B; + B = temp; + } + ps += 4; + for (i = 0; i < 16; i++) { + temp = A + FI(B, C, D) + cwp[(int) (*pp++)] + *pc++; + temp = rotl32(temp, ps[i & 3]); + temp += B; + A = D; + D = C; + C = B; + B = temp; + } + +# endif /* MD5_SIZE_VS_SPEED > 2 */ +#else + /* First round: using the given function, the context and a constant + the next context is computed. Because the algorithms processing + unit is a 32-bit word and it is determined to work on words in + little endian byte order we perhaps have to change the byte order + before the computation. To reduce the work for the next steps + we store the swapped words in the array CORRECT_WORDS. */ +# define OP(a, b, c, d, s, T) \ + do { \ + a += FF(b, c, d) + (*cwp++ = SWAP_LE32(*words)) + T; \ + ++words; \ + a = rotl32(a, s); \ + a += b; \ + } while (0) + + /* Before we start, one word to the strange constants. + They are defined in RFC 1321 as + T[i] = (int)(4294967296.0 * fabs(sin(i))), i=1..64 + */ + +# if MD5_SIZE_VS_SPEED == 1 + const uint32_t *pc; + const char *pp; + int i; +# endif /* MD5_SIZE_VS_SPEED */ + + /* Round 1. */ +# if MD5_SIZE_VS_SPEED == 1 + pc = C_array; + for (i = 0; i < 4; i++) { + OP(A, B, C, D, 7, *pc++); + OP(D, A, B, C, 12, *pc++); + OP(C, D, A, B, 17, *pc++); + OP(B, C, D, A, 22, *pc++); + } +# else + OP(A, B, C, D, 7, 0xd76aa478); + OP(D, A, B, C, 12, 0xe8c7b756); + OP(C, D, A, B, 17, 0x242070db); + OP(B, C, D, A, 22, 0xc1bdceee); + OP(A, B, C, D, 7, 0xf57c0faf); + OP(D, A, B, C, 12, 0x4787c62a); + OP(C, D, A, B, 17, 0xa8304613); + OP(B, C, D, A, 22, 0xfd469501); + OP(A, B, C, D, 7, 0x698098d8); + OP(D, A, B, C, 12, 0x8b44f7af); + OP(C, D, A, B, 17, 0xffff5bb1); + OP(B, C, D, A, 22, 0x895cd7be); + OP(A, B, C, D, 7, 0x6b901122); + OP(D, A, B, C, 12, 0xfd987193); + OP(C, D, A, B, 17, 0xa679438e); + OP(B, C, D, A, 22, 0x49b40821); +# endif /* MD5_SIZE_VS_SPEED == 1 */ + + /* For the second to fourth round we have the possibly swapped words + in CORRECT_WORDS. Redefine the macro to take an additional first + argument specifying the function to use. */ +# undef OP +# define OP(f, a, b, c, d, k, s, T) \ + do { \ + a += f(b, c, d) + correct_words[k] + T; \ + a = rotl32(a, s); \ + a += b; \ + } while (0) + + /* Round 2. */ +# if MD5_SIZE_VS_SPEED == 1 + pp = P_array; + for (i = 0; i < 4; i++) { + OP(FG, A, B, C, D, (int) (*pp++), 5, *pc++); + OP(FG, D, A, B, C, (int) (*pp++), 9, *pc++); + OP(FG, C, D, A, B, (int) (*pp++), 14, *pc++); + OP(FG, B, C, D, A, (int) (*pp++), 20, *pc++); + } +# else + OP(FG, A, B, C, D, 1, 5, 0xf61e2562); + OP(FG, D, A, B, C, 6, 9, 0xc040b340); + OP(FG, C, D, A, B, 11, 14, 0x265e5a51); + OP(FG, B, C, D, A, 0, 20, 0xe9b6c7aa); + OP(FG, A, B, C, D, 5, 5, 0xd62f105d); + OP(FG, D, A, B, C, 10, 9, 0x02441453); + OP(FG, C, D, A, B, 15, 14, 0xd8a1e681); + OP(FG, B, C, D, A, 4, 20, 0xe7d3fbc8); + OP(FG, A, B, C, D, 9, 5, 0x21e1cde6); + OP(FG, D, A, B, C, 14, 9, 0xc33707d6); + OP(FG, C, D, A, B, 3, 14, 0xf4d50d87); + OP(FG, B, C, D, A, 8, 20, 0x455a14ed); + OP(FG, A, B, C, D, 13, 5, 0xa9e3e905); + OP(FG, D, A, B, C, 2, 9, 0xfcefa3f8); + OP(FG, C, D, A, B, 7, 14, 0x676f02d9); + OP(FG, B, C, D, A, 12, 20, 0x8d2a4c8a); +# endif /* MD5_SIZE_VS_SPEED == 1 */ + + /* Round 3. */ +# if MD5_SIZE_VS_SPEED == 1 + for (i = 0; i < 4; i++) { + OP(FH, A, B, C, D, (int) (*pp++), 4, *pc++); + OP(FH, D, A, B, C, (int) (*pp++), 11, *pc++); + OP(FH, C, D, A, B, (int) (*pp++), 16, *pc++); + OP(FH, B, C, D, A, (int) (*pp++), 23, *pc++); + } +# else + OP(FH, A, B, C, D, 5, 4, 0xfffa3942); + OP(FH, D, A, B, C, 8, 11, 0x8771f681); + OP(FH, C, D, A, B, 11, 16, 0x6d9d6122); + OP(FH, B, C, D, A, 14, 23, 0xfde5380c); + OP(FH, A, B, C, D, 1, 4, 0xa4beea44); + OP(FH, D, A, B, C, 4, 11, 0x4bdecfa9); + OP(FH, C, D, A, B, 7, 16, 0xf6bb4b60); + OP(FH, B, C, D, A, 10, 23, 0xbebfbc70); + OP(FH, A, B, C, D, 13, 4, 0x289b7ec6); + OP(FH, D, A, B, C, 0, 11, 0xeaa127fa); + OP(FH, C, D, A, B, 3, 16, 0xd4ef3085); + OP(FH, B, C, D, A, 6, 23, 0x04881d05); + OP(FH, A, B, C, D, 9, 4, 0xd9d4d039); + OP(FH, D, A, B, C, 12, 11, 0xe6db99e5); + OP(FH, C, D, A, B, 15, 16, 0x1fa27cf8); + OP(FH, B, C, D, A, 2, 23, 0xc4ac5665); +# endif /* MD5_SIZE_VS_SPEED == 1 */ + + /* Round 4. */ +# if MD5_SIZE_VS_SPEED == 1 + for (i = 0; i < 4; i++) { + OP(FI, A, B, C, D, (int) (*pp++), 6, *pc++); + OP(FI, D, A, B, C, (int) (*pp++), 10, *pc++); + OP(FI, C, D, A, B, (int) (*pp++), 15, *pc++); + OP(FI, B, C, D, A, (int) (*pp++), 21, *pc++); + } +# else + OP(FI, A, B, C, D, 0, 6, 0xf4292244); + OP(FI, D, A, B, C, 7, 10, 0x432aff97); + OP(FI, C, D, A, B, 14, 15, 0xab9423a7); + OP(FI, B, C, D, A, 5, 21, 0xfc93a039); + OP(FI, A, B, C, D, 12, 6, 0x655b59c3); + OP(FI, D, A, B, C, 3, 10, 0x8f0ccc92); + OP(FI, C, D, A, B, 10, 15, 0xffeff47d); + OP(FI, B, C, D, A, 1, 21, 0x85845dd1); + OP(FI, A, B, C, D, 8, 6, 0x6fa87e4f); + OP(FI, D, A, B, C, 15, 10, 0xfe2ce6e0); + OP(FI, C, D, A, B, 6, 15, 0xa3014314); + OP(FI, B, C, D, A, 13, 21, 0x4e0811a1); + OP(FI, A, B, C, D, 4, 6, 0xf7537e82); + OP(FI, D, A, B, C, 11, 10, 0xbd3af235); + OP(FI, C, D, A, B, 2, 15, 0x2ad7d2bb); + OP(FI, B, C, D, A, 9, 21, 0xeb86d391); +# endif /* MD5_SIZE_VS_SPEED == 1 */ +#endif /* MD5_SIZE_VS_SPEED > 1 */ + + /* Add the starting values of the context. */ + A += A_save; + B += B_save; + C += C_save; + D += D_save; + + /* Put checksum in context given as argument. */ + ctx->A = A; + ctx->B = B; + ctx->C = C; + ctx->D = D; +} + +/* Feed data through a temporary buffer to call md5_hash_aligned_block() + * with chunks of data that are 4-byte aligned and a multiple of 64 bytes. + * This function's internal buffer remembers previous data until it has 64 + * bytes worth to pass on. Call md5_end() to flush this buffer. */ +void md5_hash(const void *buffer, size_t len, md5_ctx_t *ctx) +{ + char *buf = (char *)buffer; + + /* RFC 1321 specifies the possible length of the file up to 2^64 bits, + * Here we only track the number of bytes. */ + ctx->total += len; + + /* Process all input. */ + while (len) { + unsigned i = 64 - ctx->buflen; + + /* Copy data into aligned buffer. */ + if (i > len) i = len; + memcpy(ctx->buffer + ctx->buflen, buf, i); + len -= i; + ctx->buflen += i; + buf += i; + + /* When buffer fills up, process it. */ + if (ctx->buflen == 64) { + md5_hash_block(ctx->buffer, ctx); + ctx->buflen = 0; + } + } +} + +/* Process the remaining bytes in the buffer and put result from CTX + * in first 16 bytes following RESBUF. The result is always in little + * endian byte order, so that a byte-wise output yields to the wanted + * ASCII representation of the message digest. + */ +void md5_end(void *resbuf, md5_ctx_t *ctx) +{ + char *buf = ctx->buffer; + int i; + + /* Pad data to block size. */ + buf[ctx->buflen++] = 0x80; + memset(buf + ctx->buflen, 0, 128 - ctx->buflen); + + /* Put the 64-bit file length in *bits* at the end of the buffer. */ + ctx->total <<= 3; + if (ctx->buflen > 56) + buf += 64; + for (i = 0; i < 8; i++) + buf[56 + i] = ctx->total >> (i*8); + + /* Process last bytes. */ + if (buf != ctx->buffer) + md5_hash_block(ctx->buffer, ctx); + md5_hash_block(buf, ctx); + + /* The MD5 result is in little endian byte order. + * We (ab)use the fact that A-D are consecutive in memory. + */ +#if MD5_BIG_ENDIAN + ctx->A = SWAP_LE32(ctx->A); + ctx->B = SWAP_LE32(ctx->B); + ctx->C = SWAP_LE32(ctx->C); + ctx->D = SWAP_LE32(ctx->D); +#endif + memcpy(resbuf, &ctx->A, sizeof(ctx->A) * 4); +} diff --git a/lib/plugins/Python_hash.h b/lib/plugins/Python_hash.h new file mode 100644 index 00000000..cc1d2c43 --- /dev/null +++ b/lib/plugins/Python_hash.h @@ -0,0 +1,29 @@ +/* vi: set sw=4 ts=4: */ +/* + * md5.c - Compute MD5 checksum of strings according to the + * definition of MD5 in RFC 1321 from April 1992. + * + * Written by Ulrich Drepper , 1995. + * + * Copyright (C) 1995-1999 Free Software Foundation, Inc. + * Copyright (C) 2001 Manuel Novoa III + * Copyright (C) 2003 Glenn L. McGrath + * Copyright (C) 2003 Erik Andersen + * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. + */ + +#define MD5_RESULT_LEN 16 + +typedef struct md5_ctx_t { + uint32_t A; + uint32_t B; + uint32_t C; + uint32_t D; + uint64_t total; + uint32_t buflen; + char buffer[128]; +} md5_ctx_t; +void md5_begin(md5_ctx_t *ctx); +void md5_hash(const void *data, size_t length, md5_ctx_t *ctx); +void md5_end(void *resbuf, md5_ctx_t *ctx); diff --git a/lib/plugins/RHTSupport.conf b/lib/plugins/RHTSupport.conf new file mode 100644 index 00000000..ed2c3a85 --- /dev/null +++ b/lib/plugins/RHTSupport.conf @@ -0,0 +1,9 @@ +Enabled = yes + +URL = https://api.access.redhat.com/rs +# No means that ssl certificates will not be checked +SSLVerify = yes +# Your login has to exist +Login = +# Your password +Password = diff --git a/lib/plugins/RHTSupport.cpp b/lib/plugins/RHTSupport.cpp new file mode 100644 index 00000000..c0f03c17 --- /dev/null +++ b/lib/plugins/RHTSupport.cpp @@ -0,0 +1,312 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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. +*/ + +#define _GNU_SOURCE 1 /* for stpcpy */ +#include +#include "abrtlib.h" +#include "abrt_curl.h" +#include "abrt_rh_support.h" +#include "crash_types.h" +#include "debug_dump.h" +#include "abrt_exception.h" +#include "comm_layer_inner.h" +#include "RHTSupport.h" +#ifdef HAVE_CONFIG_H +# include +#endif + +using namespace std; + + +#if 0 //unused +static char *xml_escape(const char *str) +{ + const char *s = str; + unsigned count = 1; /* for NUL */ + while (*s) + { + if (*s == '&') + count += sizeof("&")-2; + if (*s == '<') + count += sizeof("<")-2; + if (*s == '>') + count += sizeof(">")-2; + if ((unsigned char)*s > 126 || (unsigned char)*s < ' ') + count += sizeof("\\x00")-2; + count++; + s++; + } + char *result = (char*)xmalloc(count); + char *d = result; + s = str; + while (*s) + { + if (*s == '&') + d = stpcpy(d, "&"); + else if (*s == '<') + d = stpcpy(d, "<"); + else if (*s == '>') + d = stpcpy(d, ">"); + else + if ((unsigned char)*s > 126 + || ( (unsigned char)*s < ' ' + && *s != '\t' + && *s != '\n' + && *s != '\r' + ) + ) { + *d++ = '\\'; + *d++ = 'x'; + *d++ = "0123456789abcdef"[(unsigned char)*s >> 4]; + *d++ = "0123456789abcdef"[(unsigned char)*s & 0xf]; + } + else + *d++ = *s; + s++; + } + *d = '\0'; + return result; +} +#endif + + +/* + * CReporterRHticket + */ + +CReporterRHticket::CReporterRHticket() : + m_bSSLVerify(true), + m_sStrataURL("https://api.access.redhat.com/rs") +{} + +CReporterRHticket::~CReporterRHticket() +{} + +string CReporterRHticket::Report(const map_crash_data_t& pCrashData, + const map_plugin_settings_t& pSettings, + const char *pArgs) +{ + string retval; + + map_plugin_settings_t::const_iterator end = pSettings.end(); + map_plugin_settings_t::const_iterator it; + it = pSettings.find("URL"); + string URL = (it == end ? m_sStrataURL : it->second); + it = pSettings.find("Login"); + string login = (it == end ? m_sLogin : it->second); + it = pSettings.find("Password"); + string password = (it == end ? m_sPassword : it->second); + it = pSettings.find("SSLVerify"); + bool ssl_verify = (it == end ? m_bSSLVerify : string_to_bool(it->second.c_str())); + + const string& package = get_crash_data_item_content(pCrashData, FILENAME_PACKAGE); +// const string& component = get_crash_data_item_content(pCrashData, FILENAME_COMPONENT); +// const string& release = get_crash_data_item_content(pCrashData, FILENAME_RELEASE); +// const string& arch = get_crash_data_item_content(pCrashData, FILENAME_ARCHITECTURE); +// const string& duphash = get_crash_data_item_content(pCrashData, CD_DUPHASH); + const char *reason = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_REASON); + const char *function = get_crash_data_item_content_or_NULL(pCrashData, FILENAME_CRASH_FUNCTION); + + string summary = "[abrt] " + package; + if (function && strlen(function) < 30) + { + summary += ": "; + summary += function; + } + if (reason) + { + summary += ": "; + summary += reason; + } + + string description = "abrt version: "VERSION"\n"; + description += make_description_bz(pCrashData); + + reportfile_t* file = new_reportfile(); + + /* SELinux guys are not happy with /tmp, using /var/run/abrt */ + char *tempfile = xasprintf(LOCALSTATEDIR"/run/abrt/tmp-%lu-%lu.tar.gz", (long)getpid(), (long)time(NULL)); + + int pipe_from_parent_to_child[2]; + xpipe(pipe_from_parent_to_child); + pid_t child = fork(); + if (child == 0) + { + /* child */ + close(pipe_from_parent_to_child[1]); + xmove_fd(xopen3(tempfile, O_WRONLY | O_CREAT | O_EXCL, 0600), 1); + xmove_fd(pipe_from_parent_to_child[0], 0); + execlp("gzip", "gzip", NULL); + perror_msg_and_die("can't execute '%s'", "gzip"); + } + close(pipe_from_parent_to_child[0]); + + TAR *tar = NULL; + if (tar_fdopen(&tar, pipe_from_parent_to_child[1], tempfile, + /*fileops:(standard)*/ NULL, O_WRONLY | O_CREAT, 0644, TAR_GNU) != 0) + { + retval = "can't create temporary file in "LOCALSTATEDIR"/run/abrt"; + goto ret; + } + + { + map_crash_data_t::const_iterator it = pCrashData.begin(); + for (; it != pCrashData.end(); it++) + { + if (it->first == CD_COUNT) continue; + if (it->first == CD_DUMPDIR) continue; + if (it->first == CD_INFORMALL) continue; + if (it->first == CD_REPORTED) continue; + if (it->first == CD_MESSAGE) continue; // plugin's status message (if we already reported it yesterday) + if (it->first == FILENAME_DESCRIPTION) continue; // package description + + const char *content = it->second[CD_CONTENT].c_str(); + if (it->second[CD_TYPE] == CD_TXT) + { + reportfile_add_binding_from_string(file, it->first.c_str(), content); + } + else if (it->second[CD_TYPE] == CD_BIN) + { + const char *basename = strrchr(content, '/'); + if (basename) + basename++; + else + basename = content; + string xml_name = concat_path_file("content", basename); + reportfile_add_binding_from_namedfile(file, + /*on_disk_filename */ content, + /*binding_name */ it->first.c_str(), + /*recorded_filename*/ xml_name.c_str(), + /*binary */ 1); + if (tar_append_file(tar, (char*)content, (char*)(xml_name.c_str())) != 0) + { + retval = "can't create temporary file in "LOCALSTATEDIR"/run/abrt"; + goto ret; + } + } + } + } + + /* Write out content.xml in the tarball's root */ + { + const char *signature = reportfile_as_string(file); + unsigned len = strlen(signature); + unsigned len512 = (len + 511) & ~511; + char *block = (char*)memcpy(xzalloc(len512), signature, len); + th_set_type(tar, S_IFREG | 0644); + th_set_mode(tar, S_IFREG | 0644); + //th_set_link(tar, char *linkname); + //th_set_device(tar, dev_t device); + //th_set_user(tar, uid_t uid); + //th_set_group(tar, gid_t gid); + //th_set_mtime(tar, time_t fmtime); + th_set_path(tar, (char*)"content.xml"); + th_set_size(tar, len); + th_finish(tar); /* caclulate and store th xsum etc */ + if (th_write(tar) != 0 + || full_write(tar_fd(tar), block, len512) != len512 + || tar_close(tar) != 0 + ) { + retval = "can't create temporary file in "LOCALSTATEDIR"/run/abrt"; + goto ret; + } + tar = NULL; + } + + { + update_client(_("Creating a new case...")); + char* result = send_report_to_new_case(URL.c_str(), + login.c_str(), + password.c_str(), + ssl_verify, + summary.c_str(), + description.c_str(), + package.c_str(), + tempfile + ); + VERB3 log("post result:'%s'", result); + retval = result; + free(result); + } + + ret: + // Damn, selinux does not allow SIGKILLing our own child! wtf?? + //kill(child, SIGKILL); /* just in case */ + waitpid(child, NULL, 0); + if (tar) + tar_close(tar); + //close(pipe_from_parent_to_child[1]); - tar_close() does it itself + unlink(tempfile); + free(tempfile); + reportfile_free(file); + + if (strncasecmp(retval.c_str(), "error", 5) == 0) + { + throw CABRTException(EXCEP_PLUGIN, "%s", retval.c_str()); + } + return retval; +} + +void CReporterRHticket::SetSettings(const map_plugin_settings_t& pSettings) +{ + m_pSettings = pSettings; + + map_plugin_settings_t::const_iterator end = pSettings.end(); + map_plugin_settings_t::const_iterator it; + it = pSettings.find("URL"); + if (it != end) + { + m_sStrataURL = it->second; + } + it = pSettings.find("Login"); + if (it != end) + { + m_sLogin = it->second; + } + it = pSettings.find("Password"); + if (it != end) + { + m_sPassword = it->second; + } + it = pSettings.find("SSLVerify"); + if (it != end) + { + m_bSSLVerify = string_to_bool(it->second.c_str()); + } +} + +/* Should not be deleted (why?) */ +const map_plugin_settings_t& CReporterRHticket::GetSettings() +{ + m_pSettings["URL"] = m_sStrataURL; + m_pSettings["Login"] = m_sLogin; + m_pSettings["Password"] = m_sPassword; + m_pSettings["SSLVerify"] = m_bSSLVerify ? "yes" : "no"; + + return m_pSettings; +} + +PLUGIN_INFO(REPORTER, + CReporterRHticket, + "RHticket", + "0.0.4", + "Reports bugs to Red Hat support", + "Denys Vlasenko ", + "https://fedorahosted.org/abrt/wiki", + PLUGINS_LIB_DIR"/RHTSupport.glade"); diff --git a/lib/plugins/RHTSupport.glade b/lib/plugins/RHTSupport.glade new file mode 100644 index 00000000..64fd6c26 --- /dev/null +++ b/lib/plugins/RHTSupport.glade @@ -0,0 +1,213 @@ + + + + + + 12 + False + True + center-on-parent + abrt + normal + False + + + True + vertical + 12 + + + True + 0 + none + + + True + 6 + 12 + + + True + 5 + 2 + 12 + 6 + + + True + 0 + RHTSupport URL: + + + GTK_FILL + + + + + True + 0 + Login: + + + 1 + 2 + GTK_FILL + + + + + True + 0 + Password: + + + 2 + 3 + GTK_FILL + + + + + True + True + + + + 1 + 2 + + + + + True + True + + + + 1 + 2 + 1 + 2 + + + + + True + True + False + + + + 1 + 2 + 2 + 3 + + + + + Show password + True + True + False + 0 + True + + + 1 + 2 + 3 + 4 + + + + + 0 + + + 3 + 4 + GTK_FILL + + + + + SSL verify + True + True + False + 0 + True + + + 2 + 4 + 5 + + + + + + + + + True + <b>RHTSupport plugin configuration</b> + True + + + + + False + False + 1 + + + + + True + end + + + gtk-cancel + True + True + True + True + + + False + False + 0 + + + + + gtk-apply + True + True + True + True + + + False + False + 1 + + + + + False + end + 0 + + + + + + button2 + bApply + + + + diff --git a/lib/plugins/RHTSupport.h b/lib/plugins/RHTSupport.h new file mode 100644 index 00000000..a96a3b3d --- /dev/null +++ b/lib/plugins/RHTSupport.h @@ -0,0 +1,45 @@ +/* + Copyright (C) 2010 ABRT team + Copyright (C) 2010 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 RHTICKET_H_ +#define RHTICKET_H_ + +#include "plugin.h" +#include "reporter.h" + +class CReporterRHticket: public CReporter +{ + private: + bool m_bSSLVerify; + std::string m_sStrataURL; + std::string m_sLogin; + std::string m_sPassword; + + public: + CReporterRHticket(); + virtual ~CReporterRHticket(); + + virtual std::string Report(const map_crash_data_t& pCrashData, + const map_plugin_settings_t& pSettings, + const char *pArgs); + + virtual void SetSettings(const map_plugin_settings_t& pSettings); + virtual const map_plugin_settings_t& GetSettings(); +}; + +#endif diff --git a/lib/plugins/ReportUploader.conf b/lib/plugins/ReportUploader.conf new file mode 100644 index 00000000..57692531 --- /dev/null +++ b/lib/plugins/ReportUploader.conf @@ -0,0 +1,21 @@ +Enabled = yes + +# Customer = "Example Inc." +# Ticket = IT12345 +# Encrypt = yes +# If set to "no" or commented out, +# compressed ticket data will be copied to /tmp: +# Upload = yes + +# If "Upload = yes", URL to upload the files to. +# supported: ftp, ftps, http, https, scp, sftp, tftp, file +# for example: ftp://user:password@server.name/directory +# or: scp://user:password@server.name:port/directory etc. +# for testing: file:///tmp/test_directory +# URL = + +# How many times we try to upload the file +# RetryCount = 3 + +# How long we wait between we retry the upload (in seconds) +# RetryDelay = 20 diff --git a/lib/plugins/ReportUploader.cpp b/lib/plugins/ReportUploader.cpp new file mode 100644 index 00000000..eb01691c --- /dev/null +++ b/lib/plugins/ReportUploader.cpp @@ -0,0 +1,505 @@ +/* + ReportUploader.cpp + + Copyright (C) 2009 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 "abrtlib.h" +#include "abrt_curl.h" +#include "ReportUploader.h" +#include "debug_dump.h" +#include "abrt_exception.h" +#include "comm_layer_inner.h" + +using namespace std; + + +CReportUploader::CReportUploader() : + m_bEncrypt(false), + m_bUpload(false), + m_nRetryCount(3), + m_nRetryDelay(20) +{} + +CReportUploader::~CReportUploader() +{} + + +static void RunCommand(const char *cmd) +{ + int retcode = system(cmd); + if (retcode) + { + throw CABRTException(EXCEP_PLUGIN, "'%s' exited with %d", cmd, retcode); + } +} + +static string ReadCommand(const char *cmd) +{ + FILE* fp = popen(cmd, "r"); + if (!fp) + { + throw CABRTException(EXCEP_PLUGIN, "Error running '%s'", cmd); + } + + string result; + char buff[1024]; + while (fgets(buff, sizeof(buff), fp) != NULL) + { + strchrnul(buff, '\n')[0] = '\0'; + result += buff; + } + + int retcode = pclose(fp); + if (retcode) + { + throw CABRTException(EXCEP_PLUGIN, "'%s' exited with %d", cmd, retcode); + } + + return result; +} + +static void WriteCommand(const char *cmd, const char *input) +{ + FILE* fp = popen(cmd, "w"); + if (!fp) + { + throw CABRTException(EXCEP_PLUGIN, "error running '%s'", cmd); + } + + /* Hoping it's not too big to get us forever blocked... */ + fputs(input, fp); + + int retcode = pclose(fp); + if (retcode) + { + throw CABRTException(EXCEP_PLUGIN, "'%s' exited with %d", cmd, retcode); + } +} + +void CReportUploader::SendFile(const char *pURL, const char *pFilename, int retry_count, int retry_delay) +{ + if (pURL[0] == '\0') + { + error_msg(_("FileTransfer: URL not specified")); + return; + } + + update_client(_("Sending archive %s to %s"), pFilename, pURL); + + const char *base = (strrchr(pFilename, '/') ? : pFilename-1) + 1; + string wholeURL = concat_path_file(pURL, base); + int count = retry_count; + int result; + while (1) + { + FILE* f = fopen(pFilename, "r"); + if (!f) + { + throw CABRTException(EXCEP_PLUGIN, "Can't open archive file '%s'", pFilename); + } + struct stat buf; + fstat(fileno(f), &buf); /* never fails */ + CURL* curl = xcurl_easy_init(); + /* enable uploading */ + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); + /* specify target */ + curl_easy_setopt(curl, CURLOPT_URL, wholeURL.c_str()); + curl_easy_setopt(curl, CURLOPT_READDATA, f); + curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)buf.st_size); + /* everything is done here; result 0 means success */ + result = curl_easy_perform(curl); + /* goodbye */ + curl_easy_cleanup(curl); + fclose(f); + if (result != 0) + { + update_client(_("Sending failed, trying again. %s"), curl_easy_strerror((CURLcode)result)); + } + if (result == 0 || --count <= 0) + break; + /* retry the upload if not succesful, wait a bit before next try */ + sleep(retry_delay); + } + + if (count <= 0 && result != 0) + { + throw CABRTException(EXCEP_PLUGIN, "Curl can not send a ticket"); + } +} + + +static void write_str_to_file(const char *str, const char *path, const char *fname) +{ + string ofile_name = concat_path_file(path, fname); + FILE *ofile = fopen(ofile_name.c_str(), "w"); + if (!ofile) + { + throw CABRTException(EXCEP_PLUGIN, "Can't open '%s'", ofile_name.c_str()); + } + fputs(str, ofile); + fclose(ofile); +} + +string CReportUploader::Report(const map_crash_data_t& pCrashData, + const map_plugin_settings_t& pSettings, + const char *pArgs) +{ + string customer_name; + string ticket_name; + string upload_url; + bool do_encrypt; + bool do_upload; + int retry_count; + int retry_delay; + + /* if parse_settings fails it returns an empty map so we need to use defaults */ + map_plugin_settings_t settings = parse_settings(pSettings); + // Get ticket name, customer name, and do_encrypt from config settings + if (!settings.empty()) + { + customer_name = settings["Customer"]; + ticket_name = settings["Ticket"]; + upload_url = settings["URL"]; + do_encrypt = string_to_bool(settings["Encrypt"].c_str()); + do_upload = string_to_bool(settings["Upload"].c_str()); + retry_count = xatoi_u(settings["RetryCount"].c_str()); + retry_delay = xatoi_u(settings["RetryDelay"].c_str()); + } + else + { + customer_name = m_sCustomer; + ticket_name = m_sTicket; + upload_url = m_sURL; + do_encrypt = m_bEncrypt; + do_upload = m_bUpload; + retry_count = m_nRetryCount; + retry_delay = m_nRetryDelay; + } + update_client(_("Creating a ReportUploader report...")); + + bool have_ticket_name = (ticket_name != ""); + if (!have_ticket_name) + { + ticket_name = "ReportUploader-newticket"; + } + + // Format the time to add to the file name + char timebuf[256]; + time_t curtime = time(NULL); + strftime(timebuf, sizeof(timebuf), "-%Y%m%d%H%M%S", gmtime(&curtime)); + + // Create a tmp work directory, and within that + // create the "-yyyymmddhhmmss" directory + // which will be the root of the tarball + string file_name = ticket_name + timebuf; + + char tmpdir_name[] = "/tmp/abrtuploadXXXXXX"; + if (mkdtemp(tmpdir_name) == NULL) + { + throw CABRTException(EXCEP_PLUGIN, "Can't mkdir a temporary directory in /tmp"); + } + string tmptar_name = concat_path_file(tmpdir_name, file_name.c_str()); + + if (mkdir(tmptar_name.c_str(), 0700)) + { + throw CABRTException(EXCEP_PLUGIN, "Can't mkdir '%s'", tmptar_name.c_str()); + } + + // Copy each entry into the tarball root. + // Files are simply copied, strings are written to a file + // TODO: some files are totally useless: + // "Reported", "Message" (plugin's output), "DumpDir", + // "Description" (package description) - maybe skip those? + map_crash_data_t::const_iterator it; + for (it = pCrashData.begin(); it != pCrashData.end(); it++) + { + const char *content = it->second[CD_CONTENT].c_str(); + if (it->second[CD_TYPE] == CD_TXT) + { + write_str_to_file(content, tmptar_name.c_str(), it->first.c_str()); + } + else if (it->second[CD_TYPE] == CD_BIN) + { + string ofile_name = concat_path_file(tmptar_name.c_str(), it->first.c_str()); + if (copy_file(content, ofile_name.c_str(), 0644) < 0) + { + throw CABRTException(EXCEP_PLUGIN, + "Can't copy '%s' to '%s'", + content, + ofile_name.c_str() + ); + } + } + } + + // add ticket_name and customer name to tarball + if (have_ticket_name) + { + write_str_to_file(ticket_name.c_str(), tmptar_name.c_str(), "TICKET"); + } + if (customer_name != "") + { + write_str_to_file(customer_name.c_str(), tmptar_name.c_str(), "CUSTOMER"); + } + + // Create the compressed tarball + string outfile_basename = file_name + ".tar.gz"; + string outfile_name = concat_path_file(tmpdir_name, outfile_basename.c_str()); + string cmd = ssprintf("tar -C %s --create --gzip --file=%s %s", tmpdir_name, outfile_name.c_str(), file_name.c_str()); + RunCommand(cmd.c_str()); + + // encrypt if requested + string key; + if (do_encrypt) + { + key = ReadCommand("openssl rand -base64 48"); + + string infile_name = outfile_name; + outfile_basename += ".aes"; + outfile_name += ".aes"; + + cmd = ssprintf("openssl aes-128-cbc -in %s -out %s -pass stdin", infile_name.c_str(), outfile_name.c_str()); + WriteCommand(cmd.c_str(), key.c_str()); + } + + // generate md5sum + cmd = ssprintf("cd %s; md5sum <%s", tmpdir_name, outfile_basename.c_str()); + string md5sum = ReadCommand(cmd.c_str()); + + // upload or cp to /tmp + if (do_upload) + { + // FIXME: SendFile isn't working sometime (scp) + SendFile(upload_url.c_str(), outfile_name.c_str(), retry_count, retry_delay); + } + else + { + cmd = ssprintf("cp %s /tmp/", outfile_name.c_str()); + RunCommand(cmd.c_str()); + } + + // generate a reciept telling md5sum and encryption key + // note: do not internationalize these strings! + string msg; + if (have_ticket_name) + { + msg += "Please copy this into ticket: "; + msg += ticket_name; + msg += '\n'; + msg += "========cut here========\n"; + } + else + { + msg += "Please send this to your technical support:\n"; + msg += "========cut here========\n"; + } + if (do_upload) + { + msg += "RHUPLOAD: This report was sent to "; + msg += upload_url; + msg += '\n'; + } + else + { + msg += "RHUPLOAD: This report was copied into /tmp/:\n"; + } + if (have_ticket_name) + { + msg += "TICKET: "; + msg += ticket_name; + msg += '\n'; + } + msg += "FILE: "; + msg += outfile_basename; + msg += "\nMD5SUM: "; + msg += md5sum; + msg += '\n'; + if (do_encrypt) + { + msg += "KEY: aes-128-cbc\n"; + msg += key; + msg += '\n'; + } + msg += "==========end===========\n"; + + // warn the client (why _warn_? it's not an error, maybe update_client?): + //error_msg("%s", msg.c_str()); + + // delete the temporary directory + cmd = ssprintf("rm -rf %s", tmpdir_name); + RunCommand(cmd.c_str()); + + return msg; +} + +static bool is_string_safe(const char *str) +{ + const char *p = str; + while (*p) + { + unsigned char c = *p; + if ((c < '0' || c > '9') + && c != '_' + && c != '-' + ) { + c |= 0x20; // tolower + if (c < 'a' || c > 'z') + { + return false; + } + } + // only 0-9, -, _, A-Z, a-z reach this point + p++; + } + return true; +} + +void CReportUploader::SetSettings(const map_plugin_settings_t& pSettings) +{ + m_pSettings = pSettings; + + map_plugin_settings_t::const_iterator end = pSettings.end(); + map_plugin_settings_t::const_iterator it; + it = pSettings.find("Customer"); + if (it != end) + { + m_sCustomer = it->second; + } + // We use m_sTicket as part of filename, + // and we use resulting filename in system("cd %s; ...", filename) etc, + // so we are very paraniod about allowed chars + it = pSettings.find("Ticket"); + if (it != end && is_string_safe(it->second.c_str())) + { + m_sTicket = it->second; + } + it = pSettings.find("URL"); + if (it != end) + { + m_sURL = it->second; + } + it = pSettings.find("Encrypt"); + if (it != end) + { + m_bEncrypt = string_to_bool(it->second.c_str()); + } + it = pSettings.find("Upload"); + if (it != end) + { + m_bUpload = string_to_bool(it->second.c_str()); + } + it = pSettings.find("RetryCount"); + if (it != end) + { + m_nRetryCount = xatoi_u(it->second.c_str()); + } + it = pSettings.find("RetryDelay"); + if (it != end) + { + m_nRetryDelay = xatoi_u(it->second.c_str()); + } +} + +const map_plugin_settings_t& CReportUploader::GetSettings() +{ + m_pSettings["Customer"] = m_sCustomer; + m_pSettings["Ticket"] = m_sTicket; + m_pSettings["URL"] = m_sURL; + m_pSettings["Encrypt"] = m_bEncrypt ? "yes" : "no"; + m_pSettings["Upload"] = m_bUpload ? "yes" : "no"; + m_pSettings["RetryCount"] = to_string(m_nRetryCount); + m_pSettings["RetryDelay"] = to_string(m_nRetryDelay); + + return m_pSettings; +} + +//todo: make static +map_plugin_settings_t CReportUploader::parse_settings(const map_plugin_settings_t& pSettings) +{ + map_plugin_settings_t plugin_settings; + + map_plugin_settings_t::const_iterator end = pSettings.end(); + map_plugin_settings_t::const_iterator it; + + it = pSettings.find("Customer"); + if (it == end) + { + plugin_settings.clear(); + return plugin_settings; + } + plugin_settings["Customer"] = it->second; + + it = pSettings.find("Ticket"); + if (it == end) + { + plugin_settings.clear(); + return plugin_settings; + } + plugin_settings["Ticket"] = it->second; + + it = pSettings.find("URL"); + if (it == end) + { + plugin_settings.clear(); + return plugin_settings; + } + plugin_settings["URL"] = it->second; + + it = pSettings.find("Encrypt"); + if (it == end) + { + plugin_settings.clear(); + return plugin_settings; + } + plugin_settings["Encrypt"] = it->second; + + it = pSettings.find("Upload"); + if (it == end) + { + plugin_settings.clear(); + return plugin_settings; + } + plugin_settings["Upload"] = it->second; + + it = pSettings.find("RetryCount"); + if (it == end) + { + plugin_settings.clear(); + return plugin_settings; + } + plugin_settings["RetryCount"] = it->second; + + it = pSettings.find("RetryDelay"); + if (it == end) + { + plugin_settings.clear(); + return plugin_settings; + } + plugin_settings["RetryDelay"] = it->second; + + VERB1 log("User settings ok, using them instead of defaults"); + return plugin_settings; +} + +PLUGIN_INFO(REPORTER, + CReportUploader, + "ReportUploader", + "0.0.1", + _("Packs crash data into .tar.gz file, optionally uploads it via FTP/SCP/etc"), + "gavin@redhat.com", + "https://fedorahosted.org/abrt/wiki", + PLUGINS_LIB_DIR"/ReportUploader.glade"); diff --git a/lib/plugins/ReportUploader.glade b/lib/plugins/ReportUploader.glade new file mode 100644 index 00000000..c2bbd470 --- /dev/null +++ b/lib/plugins/ReportUploader.glade @@ -0,0 +1,249 @@ + + + + + + 5 + False + True + center-on-parent + abrt + normal + False + + + True + vertical + 2 + + + True + 0 + none + + + True + 6 + 12 + + + True + 7 + 2 + 12 + 6 + + + True + 0 + Customer: + + + GTK_FILL + + + + + True + 0 + Ticket: + + + 1 + 2 + GTK_FILL + + + + + True + 0 + URL: + + + 2 + 3 + GTK_FILL + + + + + True + True + + + + 1 + 2 + + + + + True + True + + + + 1 + 2 + 1 + 2 + + + + + True + True + + + + 1 + 2 + 2 + 3 + + + + + True + 0 + Retry count: + + + 3 + 4 + GTK_FILL + + + + + True + True + + + + 1 + 2 + 3 + 4 + + + + + True + 0 + Retry delay: + + + 4 + 5 + GTK_FILL + + + + + True + True + + + + 1 + 2 + 4 + 5 + + + + + Use encryption + True + True + False + True + + + 2 + 5 + 6 + + + + + Upload + True + True + False + True + + + 2 + 6 + 7 + + + + + + + + + True + <b>Report Uploader plugin configuration</b> + True + + + + + False + False + 1 + + + + + True + end + + + gtk-cancel + True + True + True + True + + + False + False + 0 + + + + + gtk-apply + True + True + True + True + + + False + False + 1 + + + + + False + end + 0 + + + + + + button2 + bApply + + + + diff --git a/lib/plugins/ReportUploader.h b/lib/plugins/ReportUploader.h new file mode 100644 index 00000000..4ff780b8 --- /dev/null +++ b/lib/plugins/ReportUploader.h @@ -0,0 +1,55 @@ +/* + ReportUploader.h + + Attach a configureable Ticket Number and Customer name to a report. + Create a compressed, optionally encrypted, tarball. + Upload tarball to configureable URL. + + Copyright (C) 2009 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 REPORTUPLOADER_H_ +#define REPORTUPLAODER_H_ + +#include "plugin.h" +#include "reporter.h" + +class CReportUploader : public CReporter +{ + private: + std::string m_sCustomer; + std::string m_sTicket; + std::string m_sURL; + bool m_bEncrypt; + bool m_bUpload; + int m_nRetryCount; + int m_nRetryDelay; + + void SendFile(const char *pURL, const char *pFilename, int retry_count, int retry_delay); + map_plugin_settings_t parse_settings(const map_plugin_settings_t& pSettings); + + public: + CReportUploader(); + virtual ~CReportUploader(); + virtual const map_plugin_settings_t& GetSettings(); + virtual void SetSettings(const map_plugin_settings_t& pSettings); + + virtual std::string Report(const map_crash_data_t& pCrashData, + const map_plugin_settings_t& pSettings, + const char *pArgs); +}; + +#endif diff --git a/lib/plugins/RunApp.cpp b/lib/plugins/RunApp.cpp new file mode 100644 index 00000000..69c9384c --- /dev/null +++ b/lib/plugins/RunApp.cpp @@ -0,0 +1,76 @@ +/* + RunApp.cpp + + Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) + Copyright (C) 2009 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 "abrtlib.h" +#include "RunApp.h" +#include "debug_dump.h" +#include "abrt_exception.h" +#include "comm_layer_inner.h" +#include "abrtlib.h" + +#define COMMAND 0 +#define FILENAME 1 + +using namespace std; + +void CActionRunApp::Run(const char *pActionDir, const char *pArgs, int force) +{ + /* Don't update_client() - actions run at crash time, there is no client + * to talk to at that point */ + log("RunApp('%s','%s')", pActionDir, pArgs); + + vector_string_t args; + parse_args(pArgs, args, '"'); + + if (args.size() <= COMMAND) + { + return; + } + const char *cmd = args[COMMAND].c_str(); + if (!cmd[0]) + { + return; + } + + /* NB: we chdir to the dump dir. Command can analyze component and such. + * Example: + * test x"`cat component`" = x"xorg-x11-apps" && cp /var/log/Xorg.0.log . + */ + size_t cmd_out_size; + char *cmd_out = run_in_shell_and_save_output(/*flags:*/ 0, cmd, pActionDir, &cmd_out_size); + + if (args.size() > FILENAME) + { + CDebugDump dd; + dd.Open(pActionDir); + dd.SaveBinary(args[FILENAME].c_str(), cmd_out, cmd_out_size); + } + + free(cmd_out); +} + +PLUGIN_INFO(ACTION, + CActionRunApp, + "RunApp", + "0.0.1", + _("Runs a command, saves its output"), + "zprikryl@redhat.com", + "https://fedorahosted.org/abrt/wiki", + ""); diff --git a/lib/plugins/RunApp.h b/lib/plugins/RunApp.h new file mode 100644 index 00000000..58f572d0 --- /dev/null +++ b/lib/plugins/RunApp.h @@ -0,0 +1,34 @@ +/* + RunApp.h - Simple action plugin which execute command + + Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) + Copyright (C) 2009 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 RUNAPP_H_ +#define RUNAPP_H_ + +#include "action.h" +#include +#include + +class CActionRunApp : public CAction +{ + public: + virtual void Run(const char *pActionDir, const char *pArgs, int force); +}; + +#endif diff --git a/lib/plugins/SOSreport.conf b/lib/plugins/SOSreport.conf new file mode 100644 index 00000000..3201c6da --- /dev/null +++ b/lib/plugins/SOSreport.conf @@ -0,0 +1 @@ +Enabled = yes diff --git a/lib/plugins/SOSreport.cpp b/lib/plugins/SOSreport.cpp new file mode 100644 index 00000000..6c8ab1f8 --- /dev/null +++ b/lib/plugins/SOSreport.cpp @@ -0,0 +1,162 @@ +/* + SOSreport.cpp + + Copyright (C) 2009 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 "abrtlib.h" +#include "abrt_types.h" +#include "abrt_exception.h" +#include "SOSreport.h" +#include "debug_dump.h" +#include "abrt_exception.h" +#include "comm_layer_inner.h" + +using namespace std; + +static char *ParseFilename(char *p) +{ + /* + the sosreport's filename is embedded in sosreport's output. + It appears on the line after the string in 'sosreport_filename_marker', + it has leading spaces, and a trailing newline. This function trims + any leading and trailing whitespace from the filename. + */ + static const char sosreport_filename_marker[] = + "Your sosreport has been generated and saved in:"; + + p = strstr(p, sosreport_filename_marker); + if (!p) + return p; + p = skip_whitespace(p + sizeof(sosreport_filename_marker)-1); + char *end = strchrnul(p, '\n'); + while (end > p && isspace(*end)) + *end-- = '\0'; + return p[0] == '/' ? p : NULL; +} + +void CActionSOSreport::Run(const char *pActionDir, const char *pArgs, int force) +{ + if (!force) + { + CDebugDump dd; + dd.Open(pActionDir); + bool bt_exists = dd.Exist("sosreport.tar.bz2") || dd.Exist("sosreport.tar.xz"); + if (bt_exists) + { + VERB3 log("%s already exists, not regenerating", "sosreport.tar.bz2"); + return; + } + } + + static const char command_default[] = + "cd -- '%s' || exit 1;" + "nice sosreport --tmp-dir . --batch" + " --only=anaconda --only=bootloader" + " --only=devicemapper --only=filesys --only=hardware --only=kernel" + " --only=libraries --only=memory --only=networking --only=nfsserver" + " --only=pam --only=process --only=rpm -k rpm.rpmva=off --only=ssh" + " --only=startup --only=yum 2>&1;" + "rm sosreport*.md5 2>/dev/null;" + "mv sosreport*.tar.bz2 sosreport.tar.bz2 2>/dev/null;" + "mv sosreport*.tar.xz sosreport.tar.xz 2>/dev/null;" + ; + static const char command_prefix[] = + "cd -- '%s' || exit 1;" + "nice sosreport --tmp-dir . --batch %s 2>&1;" + "rm sosreport*.md5 2>/dev/null;" + "mv sosreport*.tar.bz2 sosreport.tar.bz2 2>/dev/null;" + "mv sosreport*.tar.xz sosreport.tar.xz 2>/dev/null;" + ; + string command; + + vector_string_t args; + parse_args(pArgs, args, '"'); + + if (args.size() == 0 || args[0] == "") + { + command = ssprintf(command_default, pActionDir); + } + else + { + command = ssprintf(command_prefix, pActionDir, args[0].c_str()); + } + + update_client(_("Running sosreport: %s"), command.c_str()); + string output = command; + output += '\n'; + char *command_out = run_in_shell_and_save_output(/*flags:*/ 0, command.c_str(), /*dir:*/ NULL, /*size_p:*/ NULL); + output += command_out; + update_client(_("Finished running sosreport")); + VERB3 log("sosreport output:'%s'", output.c_str()); + +// Not needed: now we use "sosreport --tmp-dir DUMPDIR" +#if 0 + // Parse: + // "Your sosreport has been generated and saved in: + // /tmp/sosreport-XXXX.tar.bz2" + // Note: ParseFilename modifies its parameter and returns pointer + // which points somewhere inside it. + char *sosreport_filename = xstrdup(ParseFilename(command_out)); + free(command_out); + if (!sosreport_filename) + { + throw CABRTException(EXCEP_PLUGIN, "Can't find filename in sosreport output"); + } + + string sosreport_dd_filename = concat_path_file(pActionDir, "sosreport.tar"); + char *ext = strrchr(sosreport_filename, '.'); + if (ext && strcmp(ext, ".tar") != 0) + { + // Assuming it's .bz2, .gz or some such + sosreport_dd_filename += ext; + } + CDebugDump dd; + dd.Open(pActionDir); + //Not useful: dd.SaveText("sosreportoutput", output); + off_t sz = copy_file(sosreport_filename, sosreport_dd_filename.c_str(), 0644); + + // don't want to leave sosreport-XXXX.tar.bz2 in /tmp + unlink(sosreport_filename); + // sosreport-XXXX.tar.bz2.md5 too + unsigned len = strlen(sosreport_filename); + sosreport_filename = (char*)xrealloc(sosreport_filename, len + sizeof(".md5")-1 + 1); + strcpy(sosreport_filename + len, ".md5"); + unlink(sosreport_filename); + + if (sz < 0) + { + dd.Close(); + CABRTException e(EXCEP_PLUGIN, + "Can't copy '%s' to '%s'", + sosreport_filename, + sosreport_dd_filename.c_str() + ); + free(sosreport_filename); + throw e; + } + free(sosreport_filename); +#endif +} + +PLUGIN_INFO(ACTION, + CActionSOSreport, + "SOSreport", + "0.0.2", + _("Runs sosreport, saves the output"), + "gavin@redhat.com", + "https://fedorahosted.org/abrt/wiki", + ""); diff --git a/lib/plugins/SOSreport.h b/lib/plugins/SOSreport.h new file mode 100644 index 00000000..4b32940f --- /dev/null +++ b/lib/plugins/SOSreport.h @@ -0,0 +1,31 @@ +/* + SOSreport.h - Attach an sosreport to a crash dump + + Copyright (C) 2009 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 SOSREPORT_H_ +#define SOSREPORT_H_ + +#include "action.h" + +class CActionSOSreport : public CAction +{ + public: + virtual void Run(const char *pActionDir, const char *pArgs, int force); +}; + +#endif diff --git a/lib/plugins/SQLite3.conf b/lib/plugins/SQLite3.conf new file mode 100644 index 00000000..a7617a90 --- /dev/null +++ b/lib/plugins/SQLite3.conf @@ -0,0 +1,4 @@ +# Configuration file for database plugin SQLite3 + +# DB path +DBPath = /var/spool/abrt/abrt-db diff --git a/lib/plugins/SQLite3.cpp b/lib/plugins/SQLite3.cpp new file mode 100644 index 00000000..322f15a1 --- /dev/null +++ b/lib/plugins/SQLite3.cpp @@ -0,0 +1,681 @@ +/* + SQLite3.cpp + + Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) + Copyright (C) 2009 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 "abrtlib.h" +#include "SQLite3.h" +#include "abrt_exception.h" + +using namespace std; + +#define ABRT_TABLE_VERSION 4 +#define ABRT_TABLE_VERSION_STR "4" +#define ABRT_TABLE "abrt_v"ABRT_TABLE_VERSION_STR +#define ABRT_REPRESULT_TABLE "abrt_v"ABRT_TABLE_VERSION_STR"_reportresult" +#define SQLITE3_MASTER_TABLE "sqlite_master" + +#define COL_UID "UID" +#define COL_UUID "UUID" +#define COL_INFORMALL "InformAll" +#define COL_DEBUG_DUMP_PATH "DebugDumpPath" +#define COL_COUNT "Count" +#define COL_REPORTED "Reported" +#define COL_TIME "Time" +#define COL_MESSAGE "Message" + +#define COL_REPORTER "Reporter" + +/* Is this string safe wrt SQL injection? + * PHP's mysql_real_escape_string() treats \, ', ", \x00, \n, \r, and \x1a as special. + * We are a bit more paranoid and disallow any control chars. + */ +static bool is_string_safe(const char *str) +{ +// Apparently SQLite allows unescaped newlines. More surprisingly, +// it does not unescape escaped ones - I see lines ending with \ when I do it. +// I wonder whether this is a bug in SQLite, and whether using unescaped +// newlines is a danger with other SQL servers. +// For now, I disabled newline escaping, and also allowed double quote. + const char *p = str; + while (*p) + { + unsigned char c = *p; +// if (c == '\\' && p[1] != '\0') +// { +// p += 2; +// continue; +// } + if ((c < ' ' && c != '\n') + || strchr("\\\'", c) //was: "\\\"\'" + ) { + error_msg("Probable SQL injection: '%s'", str); + return false; + } + p++; + } + return true; +} + +#ifdef UNUSED_FOR_NOW +/* Escape \n */ +static string sql_escape(const char *str) +{ + const char *s = str; + unsigned len = 0; + do + { + if (*s == '\n') + len++; + len++; + } while (*s++); + + char buf[len]; + s = str; + char *d = buf; + do + { + if (*s == '\n') + *d++ = '\\'; + *d++ = *s; + } while (*s++); + + return buf; +} +#endif + +/* Note: + * expects "SELECT * FROM ...", not "SELECT FROM ..." + */ +static void get_table(vector_database_rows_t& pTable, + sqlite3 *db, const char *fmt, ...) +{ + va_list p; + va_start(p, fmt); + char *sql = xvasprintf(fmt, p); + va_end(p); + + char **table; + int ncol, nrow; + char *err = NULL; + int ret = sqlite3_get_table(db, sql, &table, &nrow, &ncol, &err); + if (ret != SQLITE_OK) + { + string errstr = ssprintf("Error in SQL:'%s' error: %s", sql, err); + free(sql); + sqlite3_free(err); + throw CABRTException(EXCEP_PLUGIN, errstr.c_str()); + } + VERB2 log("%d rows returned by SQL:%s", nrow, sql); + free(sql); + + pTable.clear(); + int ii; + for (ii = 0; ii < nrow; ii++) + { + int jj; + database_row_t row; + for (jj = 0; jj < ncol; jj++) + { + char *val = table[jj + (ncol*ii) + ncol]; + switch (jj) + { + case 0: row.m_sUUID = val; break; + case 1: row.m_sUID = val; break; + case 2: row.m_sInformAll = val; break; + case 3: row.m_sDebugDumpDir = val; break; + case 4: row.m_sCount = val; break; + case 5: row.m_sReported = val; break; + case 6: row.m_sTime = val; break; + case 7: row.m_sMessage = val; break; + } + } + pTable.push_back(row); + + } + sqlite3_free_table(table); +} + +static int execute_sql(sqlite3 *db, const char *fmt, ...) +{ + va_list p; + va_start(p, fmt); + char *sql = xvasprintf(fmt, p); + va_end(p); + + char *err = NULL; + int ret = sqlite3_exec(db, sql, /*callback:*/ NULL, /*callback param:*/ NULL, &err); + if (ret != SQLITE_OK) + { + string errstr = ssprintf("Error in SQL:'%s' error: %s", sql, err); + free(sql); + sqlite3_free(err); + throw CABRTException(EXCEP_PLUGIN, errstr.c_str()); + } + int affected = sqlite3_changes(db); + VERB2 log("%d rows affected by SQL:%s", affected, sql); + free(sql); + + return affected; +} + +static bool exists_uuid_uid(sqlite3 *db, const char *UUID, const char *UID) +{ + vector_database_rows_t table; + get_table(table, db, + "SELECT * FROM "ABRT_TABLE + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + UUID, UID + ); + return !table.empty(); +} + +static void update_from_old_ver(sqlite3 *db, int old_version) +{ + static const char *const update_sql_commands[] = { + // v0 -> v1 + NULL, + // v1 -> v2 + "BEGIN TRANSACTION;" + "CREATE TABLE abrt_v2 (" + COL_UUID" VARCHAR NOT NULL," + COL_UID" VARCHAR NOT NULL," + COL_DEBUG_DUMP_PATH" VARCHAR NOT NULL," + COL_COUNT" INT NOT NULL DEFAULT 1," + COL_REPORTED" INT NOT NULL DEFAULT 0," + COL_TIME" VARCHAR NOT NULL DEFAULT 0," + COL_MESSAGE" VARCHAR NOT NULL DEFAULT ''," + "PRIMARY KEY ("COL_UUID","COL_UID"));" + "INSERT INTO abrt_v2 " + "SELECT "COL_UUID"," + COL_UID"," + COL_DEBUG_DUMP_PATH"," + COL_COUNT"," + COL_REPORTED"," + COL_TIME"," + COL_MESSAGE + " FROM abrt;" + "DROP TABLE abrt;" + "COMMIT;", + // v2 -> v3 + "BEGIN TRANSACTION;" + "CREATE TABLE abrt_v3 (" + COL_UUID" VARCHAR NOT NULL," + COL_UID" VARCHAR NOT NULL," + COL_DEBUG_DUMP_PATH" VARCHAR NOT NULL," + COL_COUNT" INT NOT NULL DEFAULT 1," + COL_REPORTED" INT NOT NULL DEFAULT 0," + COL_TIME" VARCHAR NOT NULL DEFAULT 0," + COL_MESSAGE" VARCHAR NOT NULL DEFAULT ''," + "PRIMARY KEY ("COL_UUID","COL_UID"));" + "INSERT INTO abrt_v3 " + "SELECT "COL_UUID"," + COL_UID"," + COL_DEBUG_DUMP_PATH"," + COL_COUNT"," + COL_REPORTED"," + COL_TIME"," + COL_MESSAGE + " FROM abrt_v2;" + "DROP TABLE abrt_v2;" + "CREATE TABLE abrt_v3_reportresult (" + COL_UUID" VARCHAR NOT NULL," + COL_UID" VARCHAR NOT NULL," + COL_REPORTER" VARCHAR NOT NULL," + COL_MESSAGE" VARCHAR NOT NULL DEFAULT ''," + "PRIMARY KEY ("COL_UUID","COL_UID","COL_REPORTER"));" + "COMMIT;", + // v3-> v4 + "BEGIN TRANSACTION;" + "CREATE TABLE abrt_v4(" + COL_UUID" VARCHAR NOT NULL," + COL_UID" VARCHAR NOT NULL," + COL_INFORMALL" INT NOT NULL DEFAULT 0," + COL_DEBUG_DUMP_PATH" VARCHAR NOT NULL," + COL_COUNT" INT NOT NULL DEFAULT 1," + COL_REPORTED" INT NOT NULL DEFAULT 0," + COL_TIME" VARCHAR NOT NULL DEFAULT 0," + COL_MESSAGE" VARCHAR NOT NULL DEFAULT ''," + "PRIMARY KEY ("COL_UUID","COL_UID"));" + "INSERT INTO abrt_v4 " + "SELECT "COL_UUID"," + COL_UID"," + "0," /* COL_INFORMALL */ + COL_DEBUG_DUMP_PATH"," + COL_COUNT"," + COL_REPORTED"," + COL_TIME"," + COL_MESSAGE + " FROM abrt_v3;" + "DROP TABLE abrt_v3;" + "UPDATE abrt_v4" + " SET "COL_UID"='0', "COL_INFORMALL"=1" + " WHERE "COL_UID"='-1';" + "CREATE TABLE abrt_v4_reportresult (" + COL_UUID" VARCHAR NOT NULL," + COL_UID" VARCHAR NOT NULL," + COL_REPORTER" VARCHAR NOT NULL," + COL_MESSAGE" VARCHAR NOT NULL DEFAULT ''," + "PRIMARY KEY ("COL_UUID","COL_UID","COL_REPORTER"));" + "INSERT INTO abrt_v4_reportresult " + "SELECT * FROM abrt_v3_reportresult;" + "DROP TABLE abrt_v3_reportresult;" + "COMMIT;", + }; + + while (old_version < ABRT_TABLE_VERSION) + { + execute_sql(db, update_sql_commands[old_version]); + old_version++; + } +} + +static bool check_table(sqlite3 *db) +{ + const char *command = "SELECT NAME FROM "SQLITE3_MASTER_TABLE" " + "WHERE TYPE='table' AND NAME like 'abrt_v%';"; + char **table; + int ncol, nrow; + char *err; + int ret = sqlite3_get_table(db, command, &table, &nrow, &ncol, &err); + if (ret != SQLITE_OK) + { + /* Should never happen */ + error_msg_and_die("SQLite3 database is corrupted"); + } + if (!nrow) + { + sqlite3_free_table(table); + return false; + } + + // table format: + // table[0]:"NAME" // table[1]:"SQL" <== field names from SELECT + // table[2]:"abrt_vNN" // table[3]:"sql" + char *tableName = table[0 + ncol]; + char *underscore = strchr(tableName, '_'); + if (underscore) + { + // It can be "abrt_vNN_something", thus using atoi(), not xatoi() + int tableVersion = atoi(underscore + 2); + sqlite3_free_table(table); + if (tableVersion < ABRT_TABLE_VERSION) + { + update_from_old_ver(db, tableVersion); + } + return true; + } + sqlite3_free_table(table); + update_from_old_ver(db, 1); + return true; +} + + +CSQLite3::CSQLite3() : + m_sDBPath(LOCALSTATEDIR "/spool/abrt/abrt-db"), + m_pDB(NULL) +{} + +CSQLite3::~CSQLite3() +{ + /* Paranoia. In C++, destructor will abort() if it was called while unwinding + * the stack and it throws an exception. + */ + try + { + DisConnect(); + m_sDBPath.clear(); + } + catch (...) + { + error_msg_and_die("Internal error"); + } +} + +void CSQLite3::DisConnect() +{ + if (m_pDB) + { + sqlite3_close(m_pDB); + m_pDB = NULL; + } +} + +void CSQLite3::Connect() +{ + int ret = sqlite3_open_v2(m_sDBPath.c_str(), + &m_pDB, + SQLITE_OPEN_READWRITE, + NULL + ); + + if (ret != SQLITE_OK) + { + if (ret != SQLITE_CANTOPEN) + { + throw CABRTException(EXCEP_PLUGIN, "Can't open database '%s': %s", m_sDBPath.c_str(), sqlite3_errmsg(m_pDB)); + } + + ret = sqlite3_open_v2(m_sDBPath.c_str(), + &m_pDB, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + NULL + ); + if (ret != SQLITE_OK) + { + throw CABRTException(EXCEP_PLUGIN, "Can't create database '%s': %s", m_sDBPath.c_str(), sqlite3_errmsg(m_pDB)); + } + } + + if (!check_table(m_pDB)) + { + execute_sql(m_pDB, + "CREATE TABLE "ABRT_TABLE" (" + COL_UUID" VARCHAR NOT NULL," + COL_UID" VARCHAR NOT NULL," + COL_INFORMALL" INT NOT NULL DEFAULT 0," + COL_DEBUG_DUMP_PATH" VARCHAR NOT NULL," + COL_COUNT" INT NOT NULL DEFAULT 1," + COL_REPORTED" INT NOT NULL DEFAULT 0," + COL_TIME" VARCHAR NOT NULL DEFAULT 0," + COL_MESSAGE" VARCHAR NOT NULL DEFAULT ''," + "PRIMARY KEY ("COL_UUID","COL_UID"));" + ); + execute_sql(m_pDB, + "CREATE TABLE "ABRT_REPRESULT_TABLE" (" + COL_UUID" VARCHAR NOT NULL," + COL_UID" VARCHAR NOT NULL," + COL_REPORTER" VARCHAR NOT NULL," + COL_MESSAGE" VARCHAR NOT NULL DEFAULT ''," + "PRIMARY KEY ("COL_UUID","COL_UID","COL_REPORTER"));" + ); + } +} + +void CSQLite3::Insert_or_Update(const char *crash_id, + bool inform_all_users, + const char *pDebugDumpPath, + const char *pTime) +{ + const char *UUID = strchr(crash_id, ':'); + if (!UUID + || !is_string_safe(crash_id) + || !is_string_safe(pDebugDumpPath) + || !is_string_safe(pTime) + ) { + return; + } + + /* Split crash_id into UID:UUID */ + unsigned uid_len = UUID - crash_id; + UUID++; + char UID[uid_len + 1]; + strncpy(UID, crash_id, uid_len); + UID[uid_len] = '\0'; + + if (!exists_uuid_uid(m_pDB, UUID, UID)) + { + execute_sql(m_pDB, + "INSERT INTO "ABRT_TABLE" (" + COL_UUID"," + COL_UID"," + COL_INFORMALL"," + COL_DEBUG_DUMP_PATH"," + COL_TIME + ")" + " VALUES ('%s','%s',%u,'%s','%s');", + UUID, UID, (unsigned)inform_all_users, pDebugDumpPath, pTime + ); + } + else + { + execute_sql(m_pDB, + "UPDATE "ABRT_TABLE + " SET "COL_COUNT"="COL_COUNT"+1,"COL_TIME"='%s'" + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + pTime, + UUID, UID + ); + } +} + +void CSQLite3::DeleteRow(const char *crash_id) +{ + const char *UUID = strchr(crash_id, ':'); + if (!UUID + || !is_string_safe(crash_id) + ) { + return; + } + + /* Split crash_id into UID:UUID */ + unsigned uid_len = UUID - crash_id; + UUID++; + char UID[uid_len + 1]; + strncpy(UID, crash_id, uid_len); + UID[uid_len] = '\0'; + + if (exists_uuid_uid(m_pDB, UUID, UID)) + { + execute_sql(m_pDB, "DELETE FROM "ABRT_TABLE + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + UUID, UID + ); + execute_sql(m_pDB, "DELETE FROM "ABRT_REPRESULT_TABLE + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + UUID, UID + ); + } + else + { + error_msg("crash_id %s is not found in DB", crash_id); + } +} + +void CSQLite3::DeleteRows_by_dir(const char *dump_dir) +{ + if (!is_string_safe(dump_dir)) + { + return; + } + + /* Get UID:UUID pair(s) to delete */ + vector_database_rows_t table; + get_table(table, m_pDB, + "SELECT * FROM "ABRT_TABLE + " WHERE "COL_DEBUG_DUMP_PATH"='%s';", + dump_dir + ); + if (table.empty()) + { + return; + } + + /* Delete from both tables */ + vector_database_rows_t::iterator it = table.begin(); + while (it != table.end()) + { + execute_sql(m_pDB, + "DELETE FROM "ABRT_REPRESULT_TABLE + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + it->m_sUUID.c_str(), it->m_sUID.c_str() + ); + it++; + } + execute_sql(m_pDB, + "DELETE FROM "ABRT_TABLE + " WHERE "COL_DEBUG_DUMP_PATH"='%s'", + dump_dir + ); +} + +void CSQLite3::SetReported(const char *crash_id, const char *pMessage) +{ + const char *UUID = strchr(crash_id, ':'); + if (!UUID + || !is_string_safe(crash_id) + || !is_string_safe(pMessage) + ) { + return; + } + + /* Split crash_id into UID:UUID */ + unsigned uid_len = UUID - crash_id; + UUID++; + char UID[uid_len + 1]; + strncpy(UID, crash_id, uid_len); + UID[uid_len] = '\0'; + + if (exists_uuid_uid(m_pDB, UUID, UID)) + { + execute_sql(m_pDB, + "UPDATE "ABRT_TABLE + " SET "COL_REPORTED"=1" + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + UUID, UID + ); + execute_sql(m_pDB, + "UPDATE "ABRT_TABLE + " SET "COL_MESSAGE"='%s'" + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + pMessage, UUID, UID + ); + } + else + { + error_msg("crash_id %s is not found in DB", crash_id); + } +} + +void CSQLite3::SetReportedPerReporter(const char *crash_id, + const char *reporter, + const char *pMessage) +{ + const char *UUID = strchr(crash_id, ':'); + if (!UUID + || !is_string_safe(crash_id) + || !is_string_safe(reporter) + || !is_string_safe(pMessage) + ) { + return; + } + + /* Split crash_id into UID:UUID */ + unsigned uid_len = UUID - crash_id; + UUID++; + char UID[uid_len + 1]; + strncpy(UID, crash_id, uid_len); + UID[uid_len] = '\0'; + + int affected_rows = execute_sql(m_pDB, + "UPDATE "ABRT_REPRESULT_TABLE + " SET "COL_MESSAGE"='%s'" + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s' AND "COL_REPORTER"='%s'", + pMessage, + UUID, UID, reporter + ); + if (!affected_rows) + { + execute_sql(m_pDB, + "INSERT INTO "ABRT_REPRESULT_TABLE + " ("COL_UUID","COL_UID","COL_REPORTER","COL_MESSAGE")" + " VALUES ('%s','%s','%s','%s');", + UUID, UID, reporter, pMessage + ); + } +} + +vector_database_rows_t CSQLite3::GetUIDData(long caller_uid) +{ + vector_database_rows_t table; + + if (caller_uid == 0) + { + get_table(table, m_pDB, "SELECT * FROM "ABRT_TABLE";"); + } + else + { + get_table(table, m_pDB, + "SELECT * FROM "ABRT_TABLE + " WHERE "COL_UID"='%ld' OR "COL_INFORMALL"=1;", + caller_uid + ); + } + return table; +} + +database_row_t CSQLite3::GetRow(const char *crash_id) +{ + const char *UUID = strchr(crash_id, ':'); + if (!UUID + || !is_string_safe(crash_id) + ) { + return database_row_t(); + } + + /* Split crash_id into UID:UUID */ + unsigned uid_len = UUID - crash_id; + UUID++; + char UID[uid_len + 1]; + strncpy(UID, crash_id, uid_len); + UID[uid_len] = '\0'; + + vector_database_rows_t table; + get_table(table, m_pDB, + "SELECT * FROM "ABRT_TABLE + " WHERE "COL_UUID"='%s' AND "COL_UID"='%s';", + UUID, UID + ); + + if (table.size() == 0) + { + return database_row_t(); + } + return table[0]; +} + +void CSQLite3::SetSettings(const map_plugin_settings_t& pSettings) +{ + m_pSettings = pSettings; + + map_plugin_settings_t::const_iterator end = pSettings.end(); + map_plugin_settings_t::const_iterator it; + it = pSettings.find("DBPath"); + if (it != end) + { + m_sDBPath = it->second; + } +} + +//ok to delete? +//const map_plugin_settings_t& CSQLite3::GetSettings() +//{ +// m_pSettings["DBPath"] = m_sDBPath; +// +// return m_pSettings; +//} + +PLUGIN_INFO(DATABASE, + CSQLite3, + "SQLite3", + "0.0.2", + _("Keeps SQLite3 database about all crashes"), + "zprikryl@redhat.com,jmoskovc@redhat.com", + "https://fedorahosted.org/abrt/wiki", + ""); diff --git a/lib/plugins/SQLite3.h b/lib/plugins/SQLite3.h new file mode 100644 index 00000000..d2af864c --- /dev/null +++ b/lib/plugins/SQLite3.h @@ -0,0 +1,56 @@ +/* + SQLite3.h + + Copyright (C) 2009 Zdenek Prikryl (zprikryl@redhat.com) + Copyright (C) 2009 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 SQLITE3_H_ +#define SQLITE3_H_ + +#include "plugin.h" +#include "database.h" + +class CSQLite3 : public CDatabase +{ + private: + std::string m_sDBPath; + sqlite3* m_pDB; + + public: + CSQLite3(); + ~CSQLite3(); + + virtual void Connect(); + virtual void DisConnect(); + + virtual void Insert_or_Update(const char *crash_id, + bool inform_all_users, + const char *pDebugDumpPath, + const char *pTime); + virtual void DeleteRow(const char *crash_id); + virtual void DeleteRows_by_dir(const char *dump_dir); + virtual void SetReported(const char *crash_id, const char *pMessage); + virtual void SetReportedPerReporter(const char *crash_id, + const char *reporter, + const char *pMessage); + virtual vector_database_rows_t GetUIDData(long caller_uid); + virtual database_row_t GetRow(const char *crash_id); + + virtual void SetSettings(const map_plugin_settings_t& pSettings); +}; + +#endif diff --git a/lib/plugins/abrt-Bugzilla.7 b/lib/plugins/abrt-Bugzilla.7 new file mode 100644 index 00000000..99bb60d1 --- /dev/null +++ b/lib/plugins/abrt-Bugzilla.7 @@ -0,0 +1,43 @@ +.TH abrt "7" "1 Jun 2009" "" +.SH NAME +Bugzilla plugin for abrt(8) +.SH DESCRIPTION +.P +.I abrt +is a daemon that watches for application crashes. When a crash occurs, +it collects the crash data and takes action according to +its configuration. This manual page describes the \fIBugzilla\fP plugin +for \fIabrt\fP. +.P +This plugin is used to report the crash to a Bugzilla instance. The +plugin will determine the package name and distribution version. The +crash data is attached to the bug report. +.SH INVOCATION +The plugin is invoked in the \fIabrt.conf\fP configuration file. +No parameters are necessary. +.SH CONFIGURATION +The \fIBugzilla.conf\fP configuration file contains several +entries in the format "Option = Value". The options are: +.SS BugzillaURL +The URL of the Bugzilla instance that you want to use, including the +path to the xmlrpc. The default is https://bugzilla.redhat.com/xmlrpc.cgi +.SS Login +Your Bugzilla login. If you have no Bugzilla account, you cannot +use the plugin. +.SS Password +Your Bugzilla password. +.SH EXAMPLES +.P +This is a snippet from the \fIabrt.conf\fP configuration file. +When something crashes, use the Bugzilla plugin: +.P +[common] +.br +ActionsAndReporters = Bugzilla +.SH "SEE ALSO" +.IR abrt (8), +.IR abrt.conf (5), +.IR abrt-plugins (7) +.SH AUTHOR +Written by Zdenek Prikryl . +Manual page written by Daniel Novotny . diff --git a/lib/plugins/abrt-FileTransfer.7 b/lib/plugins/abrt-FileTransfer.7 new file mode 100644 index 00000000..a721dd81 --- /dev/null +++ b/lib/plugins/abrt-FileTransfer.7 @@ -0,0 +1,72 @@ +.TH abrt "7" "1 Jun 2009" "" +.SH NAME +FileTransfer plugin for abrt(8) +.SH DESCRIPTION +.P +.I abrt +is a daemon that watches for application crashes. When a crash occurs, +it collects the crash data and takes action according to +its configuration. This manual page describes the \fIFileTransfer\fP plugin +for \fIabrt\fP. +.P +This plugin is used to transfer the crash report to another +machine using a file transfer protocol. The protocols supported +are FTP, FTPS, HTTP, HTTPS, SCP, SFTP, and TFTP. +.SH INVOCATION +.P +The plugin is invoked in the \fIabrt.conf\fP file, usually in the +\fIActionsAndReporters\fP option and/or the \fI[cron]\fP section. +There are two modes of invocation: +.P +* Specify \fIFileTransfer(one)\fP in ActionsAndReporters directive. +Immediately after crash is detected, the plugin transfers crash data +to the server specified in the \fIFileTransfer.conf\fP configuration file. +.P +* Specify \fIFileTransfer(store)\fP in ActionsAndReporters directive +and add \fIHH:MM = FileTransfer\fP line in [cron] section. +At the time of the crash, +the plugin stores a record of it in its internal list. +When specified time is reached, the plugin iterates through +its internal list and sends every recorded crash to the specified URL. +After that, the internal list is cleared. +.SH CONFIGURATION +The \fIFileTransfer.conf\fP configuration file contains +several entries in the format "Option = Value". The options are: +.SS URL +The URL of the server, where the crash should +be transfered, specifying the protocol, the path, +the user name and the password, for example: +.br +URL = ftp://user:passwd@server.com/path +.SS ArchiveType +The type of the archive in which to pack the crash data. +Currently, \fI.tar\fP, \fI.tar.gz\fP, \fI.tar.bz2\fP and \fI.zip\fP +are supported. The default is \fI.tar.gz\fP +.SS RetryCount +This specifies how many times the plugin will try to resend +the file if the transfer was not succesful. The plugin +waits a while before it retries the transfer: see \fIRetryDelay\fP. +The default is 3 +.SS RetryDelay +If the transfer was not succesful, the plugin will +wait some time before sending the file again. This configuration +option specifies the time in seconds. The default is 20. +.SH EXAMPLES +.P +Typical configuration in \fIabrt.conf\fP. The crash is stored +each time it happens and at midnight, all the crash data +is transferred to a central server. +.P +[common] +.br +ActionsAndReporters = FileTransfer(store) +.br +[cron] +.br +00:00 = FileTransfer +.SH "SEE ALSO" +.IR abrt (8), +.IR abrt.conf (5), +.IR abrt-plugins (7) +.SH AUTHOR +Written by Daniel Novotny . diff --git a/lib/plugins/abrt-KerneloopsReporter.7 b/lib/plugins/abrt-KerneloopsReporter.7 new file mode 100644 index 00000000..98bd3874 --- /dev/null +++ b/lib/plugins/abrt-KerneloopsReporter.7 @@ -0,0 +1,40 @@ +.TH abrt "7" "1 Jun 2009" "" +.SH NAME +KerneloopsReporter plugin for abrt(8) +.SH DESCRIPTION +.P +.I abrt +is a daemon that watches for application crashes. When a crash occurs, +it collects the crash data and takes action according to +its configuration. This manual page describes the \fIKerneloopsReporter\fP +plugin for \fIabrt\fP. +.P +This plugin is used to report the crash to the Kerneloops tracker. +.SH INVOCATION +The plugin is invoked in the \fIabrt.conf\fP configuration file. +No parameters are necessary. +.SH CONFIGURATION +The \fIKerneloopsReporter.conf\fP configuration file contains one entry: +.SS SubmitURL +The URL of the kerneloops tracker, the default is +.br +SubmitURL = http://submit.kerneloops.org/submitoops.php +.SH EXAMPLES +.P +This is a snippet from the \fIabrt.conf\fP configuration file. +Each time a kernel oops is detected, run KerneloopsReporter: +.P +[common] +.br +ActionsAndReporters = Kerneloops, KerneloopsReporter +.br +[AnalyzerActionsAndReporters] +.br +Kerneloops = KerneloopsReporter +.SH "SEE ALSO" +.IR abrt (8), +.IR abrt.conf (5), +.IR abrt-plugins (7) +.SH AUTHOR +Written by Anton Arapov . Manual +page by Daniel Novotny . diff --git a/lib/plugins/abrt-KerneloopsScanner.7 b/lib/plugins/abrt-KerneloopsScanner.7 new file mode 100644 index 00000000..ff094847 --- /dev/null +++ b/lib/plugins/abrt-KerneloopsScanner.7 @@ -0,0 +1,46 @@ +.TH abrt "7" "1 Jun 2009" "" +.SH NAME +KerneloopsScanner plugin for abrt(8) +.SH DESCRIPTION +.P +.I abrt +is a daemon that watches for application crashes. When a crash occurs, +it collects the crash data and takes action according to +its configuration. This manual page describes the \fIKerneloopsScanner\fP +plugin for \fIabrt\fP. +.P +This plugin reads the system log file (default /var/log/messages) +and stores the kernel oops crashes, which were not already +reported, to abrt's debug dump directory. +.P +To distinguish between new crashes and crashes +that were already reported, the plugin makes its own entry +in the log file, which acts as a separator. +.SH INVOCATION +The plugin is invoked in the \fIabrt.conf\fP configuration file. +No parameters are necessary. +.SH CONFIGURATION +The \fIKerneloopsScanner.conf\fP configuration file contains one entry: +.SS SysLogFile +The file to scan. The default is +.br +SysLogFile = /var/log/messages +.SH EXAMPLES +.P +This is a snippet from the \fIabrt.conf\fP configuration file. +Every 10 seconds look if there were any kernel crashes: +.P +[common] +.br +ActionsAndReporters = Kerneloops, KerneloopsScanner +.br +[cron] +.br +10 = KerneloopsScanner +.SH "SEE ALSO" +.IR abrt (8), +.IR abrt.conf (5), +.IR abrt-plugins (7) +.SH AUTHOR +Written by Anton Arapov . Manual +page by Daniel Novotny . diff --git a/lib/plugins/abrt-Logger.7 b/lib/plugins/abrt-Logger.7 new file mode 100644 index 00000000..8ae679f8 --- /dev/null +++ b/lib/plugins/abrt-Logger.7 @@ -0,0 +1,44 @@ +.TH abrt "7" "1 Jun 2009" "" +.SH NAME +Logger plugin for abrt(8) +.SH DESCRIPTION +.P +.I abrt +is a daemon that watches for application crashes. When a crash occurs, +it collects the crash data and takes action according to +its configuration. This manual page describes the \fILogger\fP plugin +for \fIabrt\fP. +.P +This plugin is used to log the crash to a file. +.P +The log will contain all the file names as well as their +content. It also contains "duplicity check": the ID +of the crash, which is used to tell whether the same +crash has happened previously. +.SH INVOCATION +The plugin is invoked in the \fIabrt.conf\fP configuration file. +No parameters are necessary. +.SH CONFIGURATION +The \fILogger.conf\fP configuration file contains +several entries in a format "Option = Value". The options are: +.SS LogPath +The path to the log file. +.SS AppendLogs +If set to "yes" (the default) \fILogger\fP will append +the report to the file, otherwise it will overwrite the file (so +only the last crash will be stored). +.SH EXAMPLES +.P +This is a snippet from the \fIabrt.conf\fP configuration file. +Log all the C/C++ application crashes: +.P +[AnalyzerActionsAndReporters] +.br +CCpp = Logger +.SH "SEE ALSO" +.IR abrt (8), +.IR abrt.conf (5), +.IR abrt-plugins (7) +.SH AUTHOR +Written by Zdenek Prikryl . Manual +page by Daniel Novotny . diff --git a/lib/plugins/abrt-Mailx.7 b/lib/plugins/abrt-Mailx.7 new file mode 100644 index 00000000..90a8bbce --- /dev/null +++ b/lib/plugins/abrt-Mailx.7 @@ -0,0 +1,57 @@ +.TH abrt "7" "1 Jun 2009" "" +.SH NAME +Mailx plugin for abrt(8) +.SH DESCRIPTION +.P +.I abrt +is a daemon that watches for application crashes. When a crash occurs, +it collects the crash data and takes action according to +its configuration. This manual page describes the \fIMailx\fP plugin +for \fIabrt\fP. +.P +This plugin is used to mail the data about the crash +to a specified mail address. +.SH INVOCATION +The plugin is invoked in the \fIabrt.conf\fP configuration file. It can take +one parameter, a subject of the mail (if it differs from the +one specified in the \fIMailx.conf\fP configuration file). +.SH CONFIGURATION +The \fIMailx.conf\fP configuration file contains +several entries in a format "Option = Value". The options are: +.SS Subject +The subject of the mail. +.SS Parameters +The \fIMailx\fP plugin executes the external "mailx" command to +send the mail. This option defines some additional command line +parameters, which should be added to the program invocation, if any. +.SS EmailFrom +The address from which the email is sent. +.SS EmailTo +The address to which the email is sent. +.SS SendBinaryData +Can be "yes" or "no". If set to "yes", the email will also +contain the binary files associated with the crash. Warning: +this can cause the emails to be large! (several MB) +.SH EXAMPLES +.P +These are snippets from the \fIabrt.conf\fP configuration file. +.P +1) Each time a crash happens, a mail is sent +.PP +[common] +.br +ActionsAndReporters = Mailx("[abrt] a crash occurs") +.P +2) When a program in a specific package (in this case "httpd") crashes, +send a mail about it. +.PP +[AnalyzerActionsAndReporters] +.br +CCpp:httpd = Mailx("[abrt] Apache crash") +.SH "SEE ALSO" +.IR abrt (8), +.IR abrt.conf (5), +.IR abrt-plugins (7) +.SH AUTHOR +Written by Zdenek Prikryl . Manual +page by Daniel Novotny . diff --git a/lib/plugins/abrt-ReportUploader.7 b/lib/plugins/abrt-ReportUploader.7 new file mode 100644 index 00000000..bd91f266 --- /dev/null +++ b/lib/plugins/abrt-ReportUploader.7 @@ -0,0 +1,55 @@ +.TH abrt "7" "9 July 2009" "" +.SH NAME +ReportUploader plugin for abrt(8) +.SH DESCRIPTION +.P +.I abrt +is a daemon which watches for application crashes. When a crash occurs, +it collects the crash data and performs some actions according to +the configuration. This manual page describes the \fIReportUploader\fP plugin +for \fIabrt\fP. +.P +This plugin will send a report to an anonymous FTP site. It's intended +for use in cases where a ticketing system is associated with the FTP site, +but the ticketing system has no way to automatically create new tickets, +or add to existing tickets. Customer Name is put in config file. +Ticket name (or number) is also put in config file. If no ticket +name is configured, assume ticketing system should create a new ticket. +This information is added to the report, the report is copied into a +compressed, optionally encrypted, tarball. Then the tarball is FTP'd +to the upload site. Then a status string is displayed to the user +showing the name of the file on the FTP site, it's MD5SUM, and +it's encryption key. This information can be pasted into a ticket +in the ticketing system. +.SH INVOCATION +The plugin is invoked in the \fIabrt.conf\fP configuration file. +No parameters are necessary. +.SH CONFIGURATION +The \fIReportUploader.conf\fP configuration file contains +entries in a format "Option = Value". The options are: +.SS Customer +This is the customer's name or other customer identifier. +.SS Ticket +This is the ticket name or number. +.SS Encrypt +"yes" for encrypt upload, anything else for not. +.SS Upload +"yes" for for upload to FTP site, anything else for copy to local /tmp. +.SS URL +URL of upload site (ie. ftp://support.com/upload). +.SH EXAMPLES +.P +This is a snippet from the \fIabrt.conf\fP configuration file. +Log all the C/C++ application crashes: +.P +[AnalyzerActionsAndReporters] +.br +CCpp = RHUpload +.SH "SEE ALSO" +.IR abrt (8), +.IR abrt.conf (5), +.IR abrt-plugins (7) +.SH AUTHOR +Plugin and man page by Gavin Romig-Koch . + + diff --git a/lib/plugins/abrt-RunApp.7 b/lib/plugins/abrt-RunApp.7 new file mode 100644 index 00000000..56a8d2b0 --- /dev/null +++ b/lib/plugins/abrt-RunApp.7 @@ -0,0 +1,43 @@ +.TH abrt "7" "1 Jun 2009" "" +.SH NAME +RunApp plugin for abrt(8) +.SH DESCRIPTION +.P +.I abrt +is a daemon that watches for application crashes. When a crash occurs, +it collects the crash data and takes action according to +its configuration. This manual page describes the \fIRunApp\fP plugin +for \fIabrt\fP. +.P +This plugin is used to run a specified application when the crash occurs. +.SH INVOCATION +The plugin is invoked in the \fIabrt.conf\fP configuration file. +The first parameter is the command to run. The second, optional +parameter specifies an output file, to which the standard +output of the program is saved. +.SH CONFIGURATION +There is no configuration file, the plugin parameters are +sufficient. +.SH EXAMPLES +.P +These are snippets from the \fIabrt.conf\fP configuration file. +.P +1) Each time something crashes, print (and save in a text file) +which processes are running on the system. +.PP +[common] +.br +ActionsAndReporters = RunApp("ps ax","processes.txt") +.P +2) When proftpd crashes, restart it. +.PP +[AnalyzerActionsAndReporters] +.br +CCpp:proftpd = RunApp("/etc/init.d/proftpd restart") +.SH "SEE ALSO" +.IR abrt (8), +.IR abrt.conf (5), +.IR abrt-plugins (7) +.SH AUTHOR +Written by Zdenek Prikryl . Manual +page by Daniel Novotny . diff --git a/lib/plugins/abrt-SQLite3.7 b/lib/plugins/abrt-SQLite3.7 new file mode 100644 index 00000000..c2b39d86 --- /dev/null +++ b/lib/plugins/abrt-SQLite3.7 @@ -0,0 +1,36 @@ +.TH abrt "7" "1 Jun 2009" "" +.SH NAME +SQLite3 database plugin for abrt(8) +.SH DESCRIPTION +.P +.I abrt +is a daemon that watches for application crashes. When a crash occurs, +it collects the crash data and takes action according to +its configuration. This manual page describes the \fISQLite3\fP database plugin +for \fIabrt\fP. +.P +This is a database plugin: \fIabrt\fP needs a database in which to store +its metadata. You can choose one by specifying "Database" in +the \fIabrt.conf\fP configuration file. Currently SQLite3 is +the only choice supported. +.SH INVOCATION +The plugin is invoked in the \fIabrt.conf\fP configuration file, like +this: +.br +[common] +.br +Database = SQLite3 +.SH CONFIGURATION +The \fISQLite3.conf\fP configuration file contains one entry: +.SS DBPath +The path to the database. +.SH EXAMPLES +see \fBINVOCATION\fP +.SH "SEE ALSO" +.IR abrt (8), +.IR abrt.conf (5), +.IR abrt-plugins (7) +.SH AUTHOR +Written by Zdenek Prikryl and Jiri +Moskovcak . Manual +page by Daniel Novotny . diff --git a/lib/plugins/abrt-plugins.7 b/lib/plugins/abrt-plugins.7 new file mode 100644 index 00000000..3a99dcbb --- /dev/null +++ b/lib/plugins/abrt-plugins.7 @@ -0,0 +1,44 @@ +.TH abrt "8" "28 May 2009" "" +.SH NAME +abrt-plugins \- plugins for the abrt crash reporter program +.SH DESCRIPTION +.P +.I abrt +is a daemon that watches for application crashes. When a crash occurs, +it collects the crash data (core file, application's command line etc.) +and takes action according to the type of application that +crashed and according to the configuration specified in the +.I abrt.conf +configuration file. +.P +Plugins allow abrt to perform various actions: for example, +to report the crash to Bugzilla, to mail the report, to transfer +the report via FTP or SCP, or to run a program that you specify. +.P +This manual page provides a list of all the manual pages for +these plugins. +.P +If you want to create your own plugin, refer to the PLUGINS-HOWTO +file in the documentation directory. +.SH INVOCATION +Each plugin is invoked in the \fIabrt.conf\fP configuration +file, in the section that is appropriate to what you +want abrt to do. +.SH CONFIGURATION +Almost every plugin has its configuration file, +stored in the \fI/etc/abrt/plugins\fP directory. +.SH "SEE ALSO" +.IR abrt (8), +.IR abrt.conf (5), +.IR abrt-Bugzilla (7), +.IR abrt-FileTransfer (7), +.IR abrt-KerneloopsReporter (7), +.IR abrt-KerneloopsScanner (7), +.IR abrt-Logger (7), +.IR abrt-Mailx (7), +.IR abrt-RunApp (7), +.IR abrt-SQLite3 (7) +.SH AUTHOR +\fIabrt\fP written by Zdeněk Přikryl and +Jiří Moskovčák . Manual page written by Daniel +Novotný . -- cgit