diff --git a/abrt.spec.in b/abrt.spec.in index 1c08ca0..b50c13d 100644 --- a/abrt.spec.in +++ b/abrt.spec.in @@ -470,6 +470,7 @@ gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || : %{_bindir}/abrt-action-generate-core-backtrace %{_bindir}/abrt-action-analyze-backtrace %{_bindir}/abrt-action-list-dsos +%{_bindir}/abrt-dedup-client %{_sbindir}/abrt-install-ccpp-hook %{_sysconfdir}/libreport/events.d/ccpp_event.conf %{_sysconfdir}/libreport/events.d/gconf_event.conf diff --git a/po/POTFILES.in b/po/POTFILES.in index d2a37de..ec72e9d 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -19,6 +19,7 @@ src/plugins/abrt-action-generate-backtrace.c src/plugins/abrt-action-generate-core-backtrace.c src/plugins/abrt-action-install-debuginfo src/plugins/abrt-action-trim-files.c +src/plugins/abrt-dedup-client.c src/plugins/abrt-dump-oops.c src/plugins/abrt-retrace-client.c src/plugins/analyze_LocalGDB.xml.in diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am index 2dc404c..0c850f7 100644 --- a/src/plugins/Makefile.am +++ b/src/plugins/Makefile.am @@ -15,7 +15,8 @@ bin_PROGRAMS = \ abrt-action-generate-backtrace \ abrt-action-generate-core-backtrace \ abrt-action-analyze-backtrace \ - abrt-retrace-client + abrt-retrace-client \ + abrt-dedup-client libexec_PROGRAMS = abrt-action-install-debuginfo-to-abrt-cache @@ -212,4 +213,20 @@ abrt_retrace_client_SOURCES = \ $(BTPARSER_LIBS) \ $(NSS_LIBS) +abrt_dedup_client_SOURCES = \ + abrt-dedup-client.c \ + https-utils.c + abrt_dedup_client_CFLAGS = \ + -I$(srcdir)/../include \ + -I$(srcdir)/../lib \ + $(NSS_CFLAGS) \ + $(GLIB_CFLAGS) \ + -D_GNU_SOURCE \ + $(LIBREPORT_CFLAGS) \ + -Wall -Wwrite-strings -Werror + abrt_dedup_client_LDADD = \ + $(LIBREPORT_LIBS) \ + $(BTPARSER_LIBS) \ + $(NSS_LIBS) + DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ diff --git a/src/plugins/abrt-dedup-client.c b/src/plugins/abrt-dedup-client.c index e69de29..cfd424f 100644 --- a/src/plugins/abrt-dedup-client.c +++ b/src/plugins/abrt-dedup-client.c @@ -0,0 +1,267 @@ +/* + Copyright (C) 2010 Red Hat, 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 +#if HAVE_LOCALE_H +# include +#endif +#include "https-utils.h" + +struct dedup_bug_data +{ + int bug_id; + char *component; + int similarity; + char *status; + char *resolution; +}; + +static const char *component = NULL; +static const char *btfile = NULL; + +/* idea from curl */ +/* escape everything except [a-zA-Z0-9\.\-_~] */ +static inline int noescape(unsigned char c) +{ + if ((c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c == '-' || c == '.' || + c == '~' || c == '_') + return 1; + + return 0; +} + +int main(int argc, char **argv) +{ + int result = 0; + setlocale(LC_ALL, ""); +#if ENABLE_NLS + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); +#endif + + enum { + OPT_verbose = 1 << 0, + OPT_syslog = 1 << 1, + OPT_insecure = 1 << 2, + OPT_url = 1 << 3, + OPT_port = 1 << 4, + OPT_headers = 1 << 5, + }; + + /* Keep enum above and order of options below in sync! */ + struct options options[] = { + OPT__VERBOSE(&g_verbose), + OPT_BOOL('s', "syslog", NULL, _("log to syslog")), + OPT_BOOL('k', "insecure", NULL, + _("allow insecure connection to dedup server")), + OPT_STRING(0, "url", &url, "URL", + _("dedup server URL")), + OPT_INTEGER(0, "port", &port, + _("dedup server port")), + OPT_BOOL(0, "headers", NULL, + _("(debug) show received HTTP headers")), + OPT_END() + }; + + const char *usage = _("abrt-dedup-client component backtrace_file [options]"); + + char *env_url = getenv("DEDUP_SERVER_URL"); + if (env_url) + url = env_url; + + char *env_port = getenv("DEDUP_SERVER_PORT"); + if (env_port) + port = xatou(env_port); + + char *env_insecure = getenv("DEDUP_SERVER_INSECURE"); + if (env_insecure) + ssl_allow_insecure = strncmp(env_insecure, "insecure", strlen("insecure")) == 0; + + unsigned opts = parse_opts(argc, argv, options, usage); + if (opts & OPT_syslog) + { + openlog(msg_prefix, 0, LOG_DAEMON); + logmode = LOGMODE_SYSLOG; + } + + if (argc - optind != 2) + show_usage_and_die(usage, options); + + if (!ssl_allow_insecure) + ssl_allow_insecure = opts & OPT_insecure; + + http_show_headers = opts & OPT_headers; + + component = argv[optind]; + btfile = argv[optind + 1]; + + int i; + /* just a simple check (eg. allows '~' which should not be present in component name) */ + for (i = 0; component[i]; ++i) + if (!noescape(component[i])) + error_msg_and_die(_("Forbidden character in component name: '%c'."), component[i]); + + FILE *fp = fopen(btfile, "r"); + if (!fp) + error_msg_and_die(_("Unable to open file '%s'."), btfile); + + struct strbuf *request_body = strbuf_new(); + strbuf_append_strf(request_body, "component=%s&backtrace=", component); + + char buf[1024]; + unsigned char c; + + /* read buffered and escape at the same time */ + /* => just use one strbuf instead of two */ + while (fgets(buf, sizeof(buf), fp)) + { + for (i = 0; buf[i]; ++i) + { + c = (unsigned char)buf[i]; + if (noescape(c)) + strbuf_append_char(request_body, c); + else + strbuf_append_strf(request_body, "%%%02X", c); + } + } + fclose(fp); + + struct strbuf *request = strbuf_new(); + strbuf_append_strf(request, + "POST /btserver.cgi HTTP/1.1\r\n" + "Host: %s\r\n" + "Content-Type: application/x-www-form-urlencoded\r\n" + "Content-Length: %d\r\n" + "Connection: close\r\n" + "\r\n" + "%s", + url, request_body->len, request_body->buf); + strbuf_free(request_body); + + /* Initialize NSS */ + SECMODModule *mod; + PK11GenericObject *cert; + nss_init(&mod, &cert); + + /* start SSL communication */ + PRFileDesc *tcp_sock, *ssl_sock; + ssl_connect(&tcp_sock, &ssl_sock); + PRInt32 written = PR_Send(tcp_sock, request->buf, request->len, + 0/*flags*/, PR_INTERVAL_NO_TIMEOUT); + if (written < 0) + { + PR_Close(ssl_sock); + alert_connection_error(); + error_msg_and_die(_("Failed to send HTTP request of length %d: NSS error %d."), + request->len, PR_GetError()); + } + + char *response = tcp_read_response(tcp_sock); + strbuf_free(request); + ssl_disconnect(ssl_sock); + if (http_show_headers) + http_print_headers(stdout, response); + + char *response_body = http_get_body(response); + char *encoding = http_get_header_value(response, "Transfer-Encoding"); + if (encoding && strcasecmp("chunked", encoding) == 0) + { + char *newbody = http_join_chunked(response_body, strlen(response_body)); + free(response_body); + response_body = newbody; + } + free(encoding); + + struct dedup_bug_data bug; + /* iterate line by line */ + char *line = response_body, *newline = response_body; + int last = 0; + while (!last) + { + /* split lines */ + while (*newline && *newline != '\n' && *newline != '\r') + ++newline; + /* end of input => last line */ + if (!*newline) + last = 1; + *newline = '\0'; + + /* we could have stopped at \r, ++newline would be \n */ + if (!last) + while (newline[1] && (newline[1] == '\n' || newline[1] == '\r')) + ++newline; + + if (sscanf(line, "%d", &bug.bug_id) != 1) + { + line = ++newline; + continue; + } + + bug.component = line; + while (*bug.component && (isspace(*bug.component) || isdigit(*bug.component))) + ++bug.component; + + bug.status = bug.component; + while (*bug.status && !isspace(*bug.status)) + ++bug.status; + *bug.status = '\0'; + ++bug.status; + + if (sscanf(bug.status, "%d", &bug.similarity) != 1) + { + line = ++newline; + continue; + } + + while (*bug.status && (isspace(*bug.status) || isdigit(*bug.status))) + ++bug.status; + + bug.resolution = NULL; + if (strncmp(bug.status, "CLOSED", strlen("CLOSED")) == 0) + { + bug.resolution = bug.status; + while (*bug.resolution && !isspace(*bug.resolution)) + ++bug.resolution; + + *bug.resolution = '\0'; + ++bug.resolution; + while (*bug.resolution && isspace(*bug.resolution)) + ++bug.resolution; + } + + printf(_("We have detected that your problem is a possible duplicate " + "of bug #%d (from %s).\n"), bug.bug_id, bug.component); + if (bug.resolution) + printf(_("It has already been closed as %s. You can check " + "Bugzilla for a solution.\n"), bug.resolution); + else + printf(_("The bug has not yet been fixed. Feel free to continue " + "reporting and provide as many information as you can.\n")); + line = ++newline; + } + + free(response); + free(response_body); + + /* Shutdown NSS. */ + nss_close(mod, cert); + + return result; +} diff --git a/src/plugins/https-utils.c b/src/plugins/https-utils.c index e5ed7b5..84177bf 100644 --- a/src/plugins/https-utils.c +++ b/src/plugins/https-utils.c @@ -324,6 +324,43 @@ char *tcp_read_response(PRFileDesc *tcp_sock) return strbuf_free_nobuf(strbuf); } +/** + * Joins HTTP response body if the Transfer-Encoding is chunked. + * @param body raw HTTP response body (response without headers) + * the function operates on the input, but returns it + * to the initial state when done + * @returns Joined HTTP response body. Caller must free the value. +*/ +char *http_join_chunked(char *body, int bodylen) +{ + struct strbuf *result = strbuf_new(); + unsigned len; + int blen = bodylen > 0 ? bodylen : strlen(body); + char prevchar; + char *cursor = body; + while (cursor - body < blen) + { + if (sscanf(cursor, "%x", &len) != 1) + break; + + /* jump to next line */ + while (*cursor && *cursor != '\n') + ++cursor; + ++cursor; + + /* split chunk and append to result */ + prevchar = cursor[len]; + cursor[len] = '\0'; + strbuf_append_str(result, cursor); + cursor[len] = prevchar; + + /* len + strlen("\r\n") */ + cursor += len + 2; + } + + return strbuf_free_nobuf(result); +} + void nss_init(SECMODModule **mod, PK11GenericObject **cert) { SECStatus sec_status; diff --git a/src/plugins/https-utils.h b/src/plugins/https-utils.h index 352de40..1961716 100644 --- a/src/plugins/https-utils.h +++ b/src/plugins/https-utils.h @@ -23,5 +23,6 @@ extern char *http_get_body(const char *message); extern int http_get_response_code(const char *message); extern void http_print_headers(FILE *file, const char *message); extern char *tcp_read_response(PRFileDesc *tcp_sock); +extern char *http_join_chunked(char *body, int bodylen); extern void nss_init(SECMODModule **mod, PK11GenericObject **cert); extern void nss_close(SECMODModule *mod, PK11GenericObject *cert);