diff options
Diffstat (limited to 'src/plugins/ReportUploader.cpp')
-rw-r--r-- | src/plugins/ReportUploader.cpp | 517 |
1 files changed, 517 insertions, 0 deletions
diff --git a/src/plugins/ReportUploader.cpp b/src/plugins/ReportUploader.cpp new file mode 100644 index 00000000..4100e996 --- /dev/null +++ b/src/plugins/ReportUploader.cpp @@ -0,0 +1,517 @@ +/* + 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 "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; + while ((buff = xmalloc_fgetline(fp)) != NULL) + { + result += buff; + free(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; + char *whole_url = concat_path_file(pURL, base); + int count = retry_count; + int result; + while (1) + { + FILE* f = fopen(pFilename, "r"); + if (!f) + { + free(whole_url); + 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, whole_url); + 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); + } + free(whole_url); + + 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) +{ + char *ofile_name = concat_path_file(path, fname); + FILE *ofile = fopen(ofile_name, "w"); + if (!ofile) + { + CABRTException e(EXCEP_PLUGIN, "Can't open '%s'", ofile_name); + free(ofile_name); + throw e; + } + free(ofile_name); + 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 "<ticketname>-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"); + } + + char *tmptar_name = concat_path_file(tmpdir_name, file_name.c_str()); + if (mkdir(tmptar_name, 0700)) + { + CABRTException e(EXCEP_PLUGIN, "Can't mkdir '%s'", tmptar_name); + free(tmptar_name); + throw e; + } + + // 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, it->first.c_str()); + } + else if (it->second[CD_TYPE] == CD_BIN) + { + char *ofile_name = concat_path_file(tmptar_name, it->first.c_str()); + if (copy_file(content, ofile_name, 0644) < 0) + { + CABRTException e(EXCEP_PLUGIN, + "Can't copy '%s' to '%s'", + content, ofile_name + ); + free(tmptar_name); + free(ofile_name); + throw e; + } + free(ofile_name); + } + } + + // add ticket_name and customer name to tarball + if (have_ticket_name) + { + write_str_to_file(ticket_name.c_str(), tmptar_name, "TICKET"); + } + if (customer_name != "") + { + write_str_to_file(customer_name.c_str(), tmptar_name, "CUSTOMER"); + } + + // Create the compressed tarball + string outfile_basename = file_name + ".tar.gz"; + char *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, 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 = append_to_malloced_string(outfile_name, ".aes"); + + cmd = ssprintf("openssl aes-128-cbc -in %s -out %s -pass stdin", infile_name.c_str(), outfile_name); + 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, retry_count, retry_delay); + } + else + { + cmd = ssprintf("cp %s /tmp/", outfile_name); + 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()); + + free(tmptar_name); + free(outfile_name); + + 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"); |