diff --git a/SOURCES/0126-lib-introduce-a-new-function-copy_file_ext.patch b/SOURCES/0126-lib-introduce-a-new-function-copy_file_ext.patch new file mode 100644 index 0000000..ed0032e --- /dev/null +++ b/SOURCES/0126-lib-introduce-a-new-function-copy_file_ext.patch @@ -0,0 +1,80 @@ +From e705c7ff8b6907422753b44ad2bd9d8293578098 Mon Sep 17 00:00:00 2001 +From: Jakub Filak +Date: Wed, 15 Apr 2015 15:17:47 +0200 +Subject: [LIBREPORT PATCH] lib: introduce a new function copy_file_ext + +The new function allows to specify UID, GID and open() flags for both +source and destination files. + +This function is need to avoid race conditions and symbolic link issues. + +Related: #1211835 + +Signed-off-by: Jakub Filak +--- + src/include/internal_libreport.h | 2 ++ + src/lib/copyfd.c | 21 ++++++++++++++++++--- + 2 files changed, 20 insertions(+), 3 deletions(-) + +diff --git a/src/include/internal_libreport.h b/src/include/internal_libreport.h +index 967324b..4c5c72a 100644 +--- a/src/include/internal_libreport.h ++++ b/src/include/internal_libreport.h +@@ -153,6 +153,8 @@ off_t copyfd_eof(int src_fd, int dst_fd, int flags); + off_t copyfd_size(int src_fd, int dst_fd, off_t size, int flags); + #define copyfd_exact_size libreport_copyfd_exact_size + void copyfd_exact_size(int src_fd, int dst_fd, off_t size); ++#define copy_file_ext libreport_copy_file_ext ++off_t copy_file_ext(const char *src_name, const char *dst_name, int mode, uid_t uid, gid_t gid, int src_flags, int dst_flags); + #define copy_file libreport_copy_file + off_t copy_file(const char *src_name, const char *dst_name, int mode); + #define copy_file_recursive libreport_copy_file_recursive +diff --git a/src/lib/copyfd.c b/src/lib/copyfd.c +index e9f429d..64fece7 100644 +--- a/src/lib/copyfd.c ++++ b/src/lib/copyfd.c +@@ -149,16 +149,16 @@ off_t copyfd_eof(int fd1, int fd2, int flags) + return full_fd_action(fd1, fd2, 0, flags); + } + +-off_t copy_file(const char *src_name, const char *dst_name, int mode) ++off_t copy_file_ext(const char *src_name, const char *dst_name, int mode, uid_t uid, gid_t gid, int src_flags, int dst_flags) + { + off_t r; +- int src = open(src_name, O_RDONLY); ++ int src = open(src_name, src_flags); + if (src < 0) + { + perror_msg("Can't open '%s'", src_name); + return -1; + } +- int dst = open(dst_name, O_WRONLY | O_TRUNC | O_CREAT, mode); ++ int dst = open(dst_name, dst_flags, mode); + if (dst < 0) + { + close(src); +@@ -167,6 +167,21 @@ off_t copy_file(const char *src_name, const char *dst_name, int mode) + } + r = copyfd_eof(src, dst, /*flags:*/ 0); + close(src); ++ if (uid != (uid_t)-1L) ++ { ++ if (fchown(dst, uid, gid) == -1) ++ { ++ perror_msg("Can't change '%s' ownership to %lu:%lu", dst_name, (long)uid, (long)gid); ++ close(dst); ++ unlink(dst_name); ++ return -1; ++ } ++ } + close(dst); + return r; + } ++ ++off_t copy_file(const char *src_name, const char *dst_name, int mode) ++{ ++ return copy_file_ext(src_name, dst_name, mode, -1, -1, O_RDONLY, O_WRONLY | O_TRUNC | O_CREAT); ++} +-- +1.8.3.1 + diff --git a/SOURCES/0127-dump_dir-allow-creating-of-a-new-dir-w-o-chowning-it.patch b/SOURCES/0127-dump_dir-allow-creating-of-a-new-dir-w-o-chowning-it.patch new file mode 100644 index 0000000..200f381 --- /dev/null +++ b/SOURCES/0127-dump_dir-allow-creating-of-a-new-dir-w-o-chowning-it.patch @@ -0,0 +1,103 @@ +From b81884dcf41a6ee84c9ef5633acd2193bee60005 Mon Sep 17 00:00:00 2001 +From: Jakub Filak +Date: Wed, 15 Apr 2015 15:19:40 +0200 +Subject: [LIBREPORT PATCH] dump_dir: allow creating of a new dir w/o chowning + it + +Split dd_create() in to dd_create_skeleton() creating the directory and +intializing struct dd* and dd_reset_ownership() updating UID and GUI to +the deemed values. + +We need this because we have to avoid situations where root is using a +directory owned by a regular user. + +Related: #1211835 + +Signed-off-by: Jakub Filak +--- + src/include/dump_dir.h | 2 ++ + src/lib/dump_dir.c | 39 ++++++++++++++++++++++++++++++++------- + 2 files changed, 34 insertions(+), 7 deletions(-) + +diff --git a/src/include/dump_dir.h b/src/include/dump_dir.h +index 124511e..71cf66f 100644 +--- a/src/include/dump_dir.h ++++ b/src/include/dump_dir.h +@@ -60,6 +60,8 @@ struct dump_dir { + void dd_close(struct dump_dir *dd); + + struct dump_dir *dd_opendir(const char *dir, int flags); ++struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode); ++int dd_reset_ownership(struct dump_dir *dd); + /* Pass uid = (uid_t)-1L to disable chown'ing of newly created files + * (IOW: if you aren't running under root): + */ +diff --git a/src/lib/dump_dir.c b/src/lib/dump_dir.c +index 28439af..fabad0b 100644 +--- a/src/lib/dump_dir.c ++++ b/src/lib/dump_dir.c +@@ -455,7 +455,10 @@ struct dump_dir *dd_opendir(const char *dir, int flags) + return dd; + } + +-/* Create a fresh empty debug dump dir. ++/* Create a fresh empty debug dump dir which is owned bu the calling user. If ++ * you want to create the directory with meaningful ownership you should ++ * consider using dd_create() function or you can modify the ownership ++ * afterwards by calling dd_reset_ownership() function. + * + * ABRT owns dump dir: + * We should not allow users to write new files or write into existing ones, +@@ -511,7 +514,7 @@ struct dump_dir *dd_opendir(const char *dir, int flags) + * this runs under 0:0 + * - clients: setroubleshootd, abrt python + */ +-struct dump_dir *dd_create(const char *dir, uid_t uid, mode_t mode) ++struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode) + { + /* a little trick to copy read bits from file mode to exec bit of dir mode*/ + mode_t dir_mode = mode | ((mode & 0444) >> 2); +@@ -601,13 +604,35 @@ struct dump_dir *dd_create(const char *dir, uid_t uid, mode_t mode) + else + error_msg("User %lu does not exist, using gid 0", (long)uid); + #endif ++ } + +- if (lchown(dir, dd->dd_uid, dd->dd_gid) == -1) +- { +- perror_msg("Can't change '%s' ownership to %lu:%lu", dir, +- (long)dd->dd_uid, (long)dd->dd_gid); +- } ++ return dd; ++} ++ ++/* Resets ownership of the given directory to UID and GID according to values ++ * in dd_create_skeleton(). ++ */ ++int dd_reset_ownership(struct dump_dir *dd) ++{ ++ const int r =lchown(dd->dd_dirname, dd->dd_uid, dd->dd_gid); ++ if (r < 0) ++ { ++ perror_msg("Can't change '%s' ownership to %lu:%lu", dd->dd_dirname, ++ (long)dd->dd_uid, (long)dd->dd_gid); + } ++ return r; ++} ++ ++/* Calls dd_create_skeleton() and dd_reset_ownership(). ++ */ ++struct dump_dir *dd_create(const char *dir, uid_t uid, mode_t mode) ++{ ++ struct dump_dir *dd = dd_create_skeleton(dir, uid, mode); ++ if (dd == NULL) ++ return NULL; ++ ++ /* ignore results */ ++ dd_reset_ownership(dd); + + return dd; + } +-- +1.8.3.1 + diff --git a/SOURCES/0127-lib-add-Problem-Format-API.patch b/SOURCES/0127-lib-add-Problem-Format-API.patch deleted file mode 100644 index 3c7ff9c..0000000 --- a/SOURCES/0127-lib-add-Problem-Format-API.patch +++ /dev/null @@ -1,2244 +0,0 @@ -From d18e0f667d33527c1c5e54f448f6aa2ba59dabdb Mon Sep 17 00:00:00 2001 -From: Jakub Filak -Date: Thu, 4 Dec 2014 08:43:17 +0100 -Subject: [PATCH 127/136] lib: add Problem Format API - -Related to #303 - -Signed-off-by: Jakub Filak ---- - po/POTFILES.in | 1 + - src/include/Makefile.am | 1 + - src/include/problem_report.h | 225 ++++++++ - src/lib/Makefile.am | 5 +- - src/lib/problem_report.c | 1209 ++++++++++++++++++++++++++++++++++++++++++ - tests/Makefile.am | 3 +- - tests/problem_report.at | 690 ++++++++++++++++++++++++ - tests/testsuite.at | 1 + - 8 files changed, 2133 insertions(+), 2 deletions(-) - create mode 100644 src/include/problem_report.h - create mode 100644 src/lib/problem_report.c - create mode 100644 tests/problem_report.at - -diff --git a/po/POTFILES.in b/po/POTFILES.in -index 00046e2..c597b11 100644 ---- a/po/POTFILES.in -+++ b/po/POTFILES.in -@@ -23,6 +23,7 @@ src/lib/ureport.c - src/lib/make_descr.c - src/lib/parse_options.c - src/lib/problem_data.c -+src/lib/problem_report.c - src/lib/run_event.c - src/plugins/abrt_rh_support.c - src/plugins/report_Bugzilla.xml.in -diff --git a/src/include/Makefile.am b/src/include/Makefile.am -index de44cda..47ba399 100644 ---- a/src/include/Makefile.am -+++ b/src/include/Makefile.am -@@ -5,6 +5,7 @@ libreport_include_HEADERS = \ - dump_dir.h \ - event_config.h \ - problem_data.h \ -+ problem_report.h \ - report.h \ - run_event.h \ - libreport_curl.h \ -diff --git a/src/include/problem_report.h b/src/include/problem_report.h -new file mode 100644 -index 0000000..30781e6 ---- /dev/null -+++ b/src/include/problem_report.h -@@ -0,0 +1,225 @@ -+/* -+ Copyright (C) 2014 ABRT team -+ Copyright (C) 2014 RedHat Inc -+ -+ This program is free software; you can redistribute it and/or modify -+ it under the terms of the GNU General Public License as published by -+ the Free Software Foundation; either version 2 of the License, or -+ (at your option) any later version. -+ -+ This program is distributed in the hope that it will be useful, -+ but WITHOUT ANY WARRANTY; without even the implied warranty of -+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ GNU General Public License for more details. -+ -+ You should have received a copy of the GNU General Public License along -+ with this program; if not, write to the Free Software Foundation, Inc., -+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -+*/ -+#ifndef LIBREPORT_PROBLEM_REPORT_H -+#define LIBREPORT_PROBLEM_REPORT_H -+ -+#include -+#include -+#include "problem_data.h" -+ -+#ifdef __cplusplus -+extern "C" { -+#endif -+ -+#define PR_SEC_SUMMARY "summary" -+#define PR_SEC_DESCRIPTION "description" -+ -+/* -+ * The problem report structure represents a problem data formatted according -+ * to a format string. -+ * -+ * A problem report is composed of well-known sections: -+ * - summary -+ * - descritpion -+ * - attach -+ * -+ * and custom sections accessed by: -+ * problem_report_get_section(); -+ */ -+struct problem_report; -+typedef struct problem_report problem_report_t; -+ -+/* -+ * Helpers for easily switching between FILE and struct strbuf -+ */ -+ -+/* -+ * Type of buffer used by Problem report -+ */ -+typedef FILE problem_report_buffer; -+ -+/* -+ * Wrapper for the proble buffer's formated output function. -+ */ -+#define problem_report_buffer_printf(buf, fmt, ...)\ -+ fprintf((buf), (fmt), ##__VA_ARGS__) -+ -+ -+/* -+ * Get a section buffer -+ * -+ * Use this function if you need to amend something to a formatted section. -+ * -+ * @param self Problem report -+ * @param section_name Name of required section -+ * @return Always valid pointer to a section buffer -+ */ -+problem_report_buffer *problem_report_get_buffer(const problem_report_t *self, -+ const char *section_name); -+ -+/* -+ * Get Summary string -+ * -+ * The returned pointer is valid as long as you perform no further output to -+ * the summary buffer. -+ * -+ * @param self Problem report -+ * @return Non-NULL pointer to summary data -+ */ -+const char *problem_report_get_summary(const problem_report_t *self); -+ -+/* -+ * Get Description string -+ * -+ * The returned pointer is valid as long as you perform no further output to -+ * the description buffer. -+ * -+ * @param self Problem report -+ * @return Non-NULL pointer to description data -+ */ -+const char *problem_report_get_description(const problem_report_t *self); -+ -+/* -+ * Get Section's string -+ * -+ * The returned pointer is valid as long as you perform no further output to -+ * the section's buffer. -+ * -+ * @param self Problem report -+ * @param section_name Name of the required section -+ * @return Non-NULL pointer to description data -+ */ -+const char *problem_report_get_section(const problem_report_t *self, -+ const char *section_name); -+ -+/* -+ * Get GList of the problem data items that are to be attached -+ * -+ * @param self Problem report -+ * @return A pointer to GList (NULL means empty list) -+ */ -+GList *problem_report_get_attachments(const problem_report_t *self); -+ -+/* -+ * Releases all resources allocated by a problem report -+ * -+ * @param self Problem report -+ */ -+void problem_report_free(problem_report_t *self); -+ -+ -+/* -+ * An enum of Extra section flags -+ */ -+enum problem_formatter_section_flags { -+ PFFF_REQUIRED = 1 << 0, ///< section must be present in the format spec -+}; -+ -+/* -+ * The problem formatter structure formats a problem data according to a format -+ * string and stores result a problem report. -+ * -+ * The problem formatter uses '%reason%' as %summary section format string, if -+ * %summary is not provided by a format string. -+ */ -+struct problem_formatter; -+typedef struct problem_formatter problem_formatter_t; -+ -+/* -+ * Constructs a new problem formatter. -+ * -+ * @return Non-NULL pointer to the new problem formatter -+ */ -+problem_formatter_t *problem_formatter_new(void); -+ -+/* -+ * Releases all resources allocated by a problem formatter -+ * -+ * @param self Problem formatter -+ */ -+void problem_formatter_free(problem_formatter_t *self); -+ -+/* -+ * Adds a new recognized section -+ * -+ * The problem formatter ignores a section in the format spec if the section is -+ * not one of the default nor added by this function. -+ * -+ * How the problem formatter handles these extra sections: -+ * -+ * A custom section is something like %description section. %description is the -+ * default section where all text (sub)sections are stored. If the formatter -+ * finds the custom section in format string, then starts storing text -+ * (sub)sections in the custom section. -+ * -+ * (%description) |:: comment -+ * (%description) | -+ * (%description) |Package:: package -+ * (%description) | -+ * (%additiona_info) |%additional_info:: -+ * (%additiona_info) |%reporter% -+ * (%additiona_info) |User:: user_name,uid -+ * (%additiona_info) | -+ * (%additiona_info) |Directories:: root,cwd -+ * -+ * -+ * @param self Problem formatter -+ * @param name Name of the added section -+ * @param flags Info about the added section -+ * @return Zero on success. -EEXIST if the name is already known by the formatter -+ */ -+int problem_formatter_add_section(problem_formatter_t *self, const char *name, int flags); -+ -+/* -+ * Loads a problem format from a string. -+ * -+ * @param self Problem formatter -+ * @param fmt Format -+ * @return Zero on success or number of warnings (e.g. missing section, -+ * unrecognized section). -+ */ -+int problem_formatter_load_string(problem_formatter_t* self, const char *fmt); -+ -+ -+/* -+ * Loads a problem format from a file. -+ * -+ * @param self Problem formatter -+ * @param pat Path to the format file -+ * @return Zero on success or number of warnings (e.g. missing section, -+ * unrecognized section). -+ */ -+int problem_formatter_load_file(problem_formatter_t* self, const char *path); -+ -+/* -+ * Creates a new problem report, formats the data according to the loaded -+ * format string and stores output in the report. -+ * -+ * @param self Problem formatter -+ * @param data Problem data to format -+ * @param report Pointer where the created problem report is to be stored -+ * @return Zero on success, otherwise non-zero value. -+ */ -+int problem_formatter_generate_report(const problem_formatter_t *self, problem_data_t *data, problem_report_t **report); -+ -+#ifdef __cplusplus -+} -+#endif -+ -+#endif // LIBREPORT_PROBLEM_REPORT_H -diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am -index 7d9722a..a0001ef 100644 ---- a/src/lib/Makefile.am -+++ b/src/lib/Makefile.am -@@ -38,6 +38,7 @@ libreport_la_SOURCES = \ - make_descr.c \ - run_event.c \ - problem_data.c \ -+ problem_report.c \ - create_dump_dir.c \ - abrt_types.c \ - parse_release.c \ -@@ -76,6 +77,7 @@ libreport_la_CPPFLAGS = \ - $(GLIB_CFLAGS) \ - $(GOBJECT_CFLAGS) \ - $(AUGEAS_CFLAGS) \ -+ $(SATYR_CFLAGS) \ - -D_GNU_SOURCE - libreport_la_LDFLAGS = \ - -version-info 0:1:0 -@@ -84,7 +86,8 @@ libreport_la_LIBADD = \ - $(GLIB_LIBS) \ - $(JOURNAL_LIBS) \ - $(GOBJECT_LIBS) \ -- $(AUGEAS_LIBS) -+ $(AUGEAS_LIBS) \ -+ $(SATYR_LIBS) - - libreportconfdir = $(CONF_DIR) - dist_libreportconf_DATA = \ -diff --git a/src/lib/problem_report.c b/src/lib/problem_report.c -new file mode 100644 -index 0000000..0afc1ca ---- /dev/null -+++ b/src/lib/problem_report.c -@@ -0,0 +1,1209 @@ -+/* -+ Copyright (C) 2014 ABRT team -+ Copyright (C) 2014 RedHat Inc -+ -+ This program is free software; you can redistribute it and/or modify -+ it under the terms of the GNU General Public License as published by -+ the Free Software Foundation; either version 2 of the License, or -+ (at your option) any later version. -+ -+ This program is distributed in the hope that it will be useful, -+ but WITHOUT ANY WARRANTY; without even the implied warranty of -+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ GNU General Public License for more details. -+ -+ You should have received a copy of the GNU General Public License along -+ with this program; if not, write to the Free Software Foundation, Inc., -+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -+*/ -+ -+#include "problem_report.h" -+#include "internal_libreport.h" -+ -+#include -+#include -+ -+#include -+ -+#define DESTROYED_POINTER (void *)0xdeadbeef -+ -+/* FORMAT: -+ * |%summary:: Hello, world -+ * |Problem description:: %bare_comment -+ * | -+ * |Package:: package -+ * | -+ * |%attach: %binary, backtrace -+ * | -+ * |%additional_info:: -+ * |%reporter% -+ * |User:: user_name,uid -+ * | -+ * |Directories:: root,cwd -+ * -+ * PARSED DATA (list of struct section_t): -+ * { -+ * section_t { -+ * .name = '%summary'; -+ * .items = { 'Hello, world' }; -+ * .children = NULL; -+ * }, -+ * section_t { -+ * .name = '%attach' -+ * .items = { '%binary', 'backtrace' }; -+ * .children = NULL; -+ * }, -+ * section_t { -+ * .name = '%description' -+ * .items = NULL; -+ * .children = { -+ * section_t { -+ * .name = 'Problem description:'; -+ * .items = { '%bare_comment' }; -+ * .children = NULL; -+ * }, -+ * section_t { -+ * .name = ''; -+ * .items = NULL; -+ * .children = NULL; -+ * }, -+ * section_t { -+ * .name = 'Package:'; -+ * .items = { 'package' }; -+ * .children = NULL; -+ * }, -+ * } -+ * }, -+ * section_t { -+ * .name = '%additional_info' -+ * .items = { '%reporter%' }; -+ * .children = { -+ * section_t { -+ * .name = 'User:'; -+ * .items = { 'user_name', 'uid' }; -+ * .children = NULL; -+ * }, -+ * section_t { -+ * .name = ''; -+ * .items = NULL; -+ * .children = NULL; -+ * }, -+ * section_t { -+ * .name = 'Directories:'; -+ * .items = { 'root', 'cwd' }; -+ * .children = NULL; -+ * }, -+ * } -+ * } -+ * } -+ */ -+struct section_t { -+ char *name; ///< name or output text (%summar, 'Package version:'); -+ GList *items; ///< list of file names and special items (%reporter, %binar, ...) -+ GList *children; ///< list of sub sections (struct section_t) -+}; -+ -+typedef struct section_t section_t; -+ -+static section_t * -+section_new(const char *name) -+{ -+ section_t *self = xmalloc(sizeof(*self)); -+ self->name = xstrdup(name); -+ self->items = NULL; -+ self->children = NULL; -+ -+ return self; -+} -+ -+static void -+section_free(section_t *self) -+{ -+ if (self == NULL) -+ return; -+ -+ free(self->name); -+ g_list_free_full(self->items, free); -+ g_list_free_full(self->children, (GDestroyNotify)section_free); -+ -+ free(self); -+} -+ -+static int -+section_name_cmp(section_t *lhs, const char *rhs) -+{ -+ return strcmp((lhs->name + 1), rhs); -+} -+ -+/* Utility functions */ -+ -+static GList* -+split_string_on_char(const char *str, char ch) -+{ -+ GList *list = NULL; -+ for (;;) -+ { -+ const char *delim = strchrnul(str, ch); -+ list = g_list_prepend(list, xstrndup(str, delim - str)); -+ if (*delim == '\0') -+ break; -+ str = delim + 1; -+ } -+ return g_list_reverse(list); -+} -+ -+static int -+compare_item_name(const char *lookup, const char *name) -+{ -+ if (lookup[0] == '-') -+ lookup++; -+ else if (strncmp(lookup, "%bare_", 6) == 0) -+ lookup += 6; -+ return strcmp(lookup, name); -+} -+ -+static int -+is_item_name_in_section(const section_t *lookup, const char *name) -+{ -+ if (g_list_find_custom(lookup->items, name, (GCompareFunc)compare_item_name)) -+ return 0; /* "found it!" */ -+ return 1; -+} -+ -+static bool is_explicit_or_forbidden(const char *name, GList *comment_fmt_spec); -+ -+static int -+is_explicit_or_forbidden_child(const section_t *master_section, const char *name) -+{ -+ if (is_explicit_or_forbidden(name, master_section->children)) -+ return 0; /* "found it!" */ -+ return 1; -+} -+ -+/* For example: 'package' belongs to '%oneline', but 'package' is used in -+ * 'Version of component', so it is not very helpful to include that file once -+ * more in another section -+ */ -+static bool -+is_explicit_or_forbidden(const char *name, GList *comment_fmt_spec) -+{ -+ return g_list_find_custom(comment_fmt_spec, name, (GCompareFunc)is_item_name_in_section) -+ || g_list_find_custom(comment_fmt_spec, name, (GCompareFunc)is_explicit_or_forbidden_child); -+} -+ -+static GList* -+load_stream(FILE *fp) -+{ -+ assert(fp); -+ -+ GList *sections = NULL; -+ section_t *master = section_new("%description"); -+ section_t *sec = NULL; -+ -+ sections = g_list_append(sections, master); -+ -+ char *line; -+ while ((line = xmalloc_fgetline(fp)) != NULL) -+ { -+ /* Skip comments */ -+ char first = *skip_whitespace(line); -+ if (first == '#') -+ goto free_line; -+ -+ /* Handle trailing backslash continuation */ -+ check_continuation: ; -+ unsigned len = strlen(line); -+ if (len && line[len-1] == '\\') -+ { -+ line[len-1] = '\0'; -+ char *next_line = xmalloc_fgetline(fp); -+ if (next_line) -+ { -+ line = append_to_malloced_string(line, next_line); -+ free(next_line); -+ goto check_continuation; -+ } -+ } -+ -+ /* We are reusing line buffer to form temporary -+ * "key\0values\0..." in its beginning -+ */ -+ bool summary_line = false; -+ char *value = NULL; -+ char *src; -+ char *dst; -+ for (src = dst = line; *src; src++) -+ { -+ char c = *src; -+ /* did we reach the value list? */ -+ if (!value && c == ':' && src[1] == ':') -+ { -+ *dst++ = '\0'; /* terminate key */ -+ src += 1; -+ value = dst; /* remember where value starts */ -+ summary_line = (strcmp(line, "%summary") == 0); -+ if (summary_line) -+ { -+ value = (src + 1); -+ break; -+ } -+ continue; -+ } -+ /* skip whitespace in value list */ -+ if (value && isspace(c)) -+ continue; -+ *dst++ = c; /* store next key or value char */ -+ } -+ -+ GList *item_list = NULL; -+ if (summary_line) -+ { -+ /* %summary is special */ -+ item_list = g_list_append(NULL, xstrdup(skip_whitespace(value))); -+ } -+ else -+ { -+ *dst = '\0'; /* terminate value (or key) */ -+ if (value) -+ item_list = split_string_on_char(value, ','); -+ } -+ -+ sec = section_new(line); -+ sec->items = item_list; -+ -+ if (sec->name[0] == '%') -+ { -+ if (!summary_line && strcmp(sec->name, "%attach") != 0) -+ { -+ master->children = g_list_reverse(master->children); -+ master = sec; -+ } -+ -+ sections = g_list_prepend(sections, sec); -+ } -+ else -+ master->children = g_list_prepend(master->children, sec); -+ -+ free_line: -+ free(line); -+ } -+ -+ /* If master equals sec, then master's children list was not yet reversed. -+ * -+ * %description is the default section (i.e is not explicitly mentioned) -+ * and %summary nor %attach cause its children list to reverse. -+ */ -+ if (master == sec || strcmp(master->name, "%description") == 0) -+ master->children = g_list_reverse(master->children); -+ -+ return sections; -+} -+ -+ -+/* Summary generation */ -+ -+#define MAX_OPT_DEPTH 10 -+static int -+format_percented_string(const char *str, problem_data_t *pd, FILE *result) -+{ -+ long old_pos[MAX_OPT_DEPTH] = { 0 }; -+ int okay[MAX_OPT_DEPTH] = { 1 }; -+ long len = 0; -+ int opt_depth = 1; -+ -+ while (*str) { -+ switch (*str) { -+ default: -+ putc(*str, result); -+ len++; -+ str++; -+ break; -+ case '\\': -+ if (str[1]) -+ str++; -+ putc(*str, result); -+ len++; -+ str++; -+ break; -+ case '[': -+ if (str[1] == '[' && opt_depth < MAX_OPT_DEPTH) -+ { -+ old_pos[opt_depth] = len; -+ okay[opt_depth] = 1; -+ opt_depth++; -+ str += 2; -+ } else { -+ putc(*str, result); -+ len++; -+ str++; -+ } -+ break; -+ case ']': -+ if (str[1] == ']' && opt_depth > 1) -+ { -+ opt_depth--; -+ if (!okay[opt_depth]) -+ { -+ fseek(result, old_pos[opt_depth], SEEK_SET); -+ len = old_pos[opt_depth]; -+ } -+ str += 2; -+ } else { -+ putc(*str, result); -+ len++; -+ str++; -+ } -+ break; -+ case '%': ; -+ char *nextpercent = strchr(++str, '%'); -+ if (!nextpercent) -+ { -+ error_msg_and_die("Unterminated %%element%%: '%s'", str - 1); -+ } -+ -+ *nextpercent = '\0'; -+ const problem_item *item = problem_data_get_item_or_NULL(pd, str); -+ *nextpercent = '%'; -+ -+ if (item && (item->flags & CD_FLAG_TXT)) -+ { -+ fputs(item->content, result); -+ len += strlen(item->content); -+ } -+ else -+ okay[opt_depth - 1] = 0; -+ str = nextpercent + 1; -+ break; -+ } -+ } -+ -+ if (opt_depth > 1) -+ { -+ error_msg_and_die("Unbalanced [[ ]] bracket"); -+ } -+ -+ if (!okay[0]) -+ { -+ error_msg("Undefined variable outside of [[ ]] bracket"); -+ } -+ -+ return 0; -+} -+ -+/* BZ comment generation */ -+ -+static int -+append_text(struct strbuf *result, const char *item_name, const char *content, bool print_item_name) -+{ -+ char *eol = strchrnul(content, '\n'); -+ if (eol[0] == '\0' || eol[1] == '\0') -+ { -+ /* one-liner */ -+ int pad = 16 - (strlen(item_name) + 2); -+ if (pad < 0) -+ pad = 0; -+ if (print_item_name) -+ strbuf_append_strf(result, -+ eol[0] == '\0' ? "%s: %*s%s\n" : "%s: %*s%s", -+ item_name, pad, "", content -+ ); -+ else -+ strbuf_append_strf(result, -+ eol[0] == '\0' ? "%s\n" : "%s", -+ content -+ ); -+ } -+ else -+ { -+ /* multi-line item */ -+ if (print_item_name) -+ strbuf_append_strf(result, "%s:\n", item_name); -+ for (;;) -+ { -+ eol = strchrnul(content, '\n'); -+ strbuf_append_strf(result, -+ /* For %bare_multiline_item, we don't want to print colons */ -+ (print_item_name ? ":%.*s\n" : "%.*s\n"), -+ (int)(eol - content), content -+ ); -+ if (eol[0] == '\0' || eol[1] == '\0') -+ break; -+ content = eol + 1; -+ } -+ } -+ return 1; -+} -+ -+static int -+append_short_backtrace(struct strbuf *result, problem_data_t *problem_data, size_t max_text_size, bool print_item_name) -+{ -+ const problem_item *item = problem_data_get_item_or_NULL(problem_data, -+ FILENAME_BACKTRACE); -+ if (!item) -+ return 0; /* "I did not print anything" */ -+ if (!(item->flags & CD_FLAG_TXT)) -+ return 0; /* "I did not print anything" */ -+ -+ char *truncated = NULL; -+ -+ if (strlen(item->content) >= max_text_size) -+ { -+ log_debug("'backtrace' exceeds the text file size, going to append its short version"); -+ -+ char *error_msg = NULL; -+ const char *analyzer = problem_data_get_content_or_NULL(problem_data, FILENAME_ANALYZER); -+ if (!analyzer) -+ { -+ log_debug("Problem data does not contain '"FILENAME_ANALYZER"' file"); -+ return 0; -+ } -+ -+ /* For CCpp crashes, use the GDB-produced backtrace which should be -+ * available by now. sr_abrt_type_from_analyzer returns SR_REPORT_CORE -+ * by default for CCpp crashes. -+ */ -+ enum sr_report_type report_type = sr_abrt_type_from_analyzer(analyzer); -+ if (strcmp(analyzer, "CCpp") == 0) -+ { -+ log_debug("Successfully identified 'CCpp' abrt type"); -+ report_type = SR_REPORT_GDB; -+ } -+ -+ struct sr_stacktrace *backtrace = sr_stacktrace_parse(report_type, -+ item->content, &error_msg); -+ -+ if (!backtrace) -+ { -+ log(_("Can't parse backtrace: %s"), error_msg); -+ free(error_msg); -+ return 0; -+ } -+ -+ /* Get optimized thread stack trace for 10 top most frames */ -+ truncated = sr_stacktrace_to_short_text(backtrace, 10); -+ sr_stacktrace_free(backtrace); -+ -+ if (!truncated) -+ { -+ log(_("Can't generate stacktrace description (no crash thread?)")); -+ return 0; -+ } -+ } -+ else -+ { -+ log_debug("'backtrace' is small enough to be included as is"); -+ } -+ -+ append_text(result, -+ /*item_name:*/ truncated ? "truncated_backtrace" : FILENAME_BACKTRACE, -+ /*content:*/ truncated ? truncated : item->content, -+ print_item_name -+ ); -+ free(truncated); -+ return 1; -+} -+ -+static int -+append_item(struct strbuf *result, const char *item_name, problem_data_t *pd, GList *comment_fmt_spec) -+{ -+ bool print_item_name = (strncmp(item_name, "%bare_", strlen("%bare_")) != 0); -+ if (!print_item_name) -+ item_name += strlen("%bare_"); -+ -+ if (item_name[0] != '%') -+ { -+ struct problem_item *item = problem_data_get_item_or_NULL(pd, item_name); -+ if (!item) -+ return 0; /* "I did not print anything" */ -+ if (!(item->flags & CD_FLAG_TXT)) -+ return 0; /* "I did not print anything" */ -+ -+ char *formatted = problem_item_format(item); -+ char *content = formatted ? formatted : item->content; -+ append_text(result, item_name, content, print_item_name); -+ free(formatted); -+ return 1; /* "I printed something" */ -+ } -+ -+ /* Special item name */ -+ -+ /* Compat with previously-existed ad-hockery: %short_backtrace */ -+ if (strcmp(item_name, "%short_backtrace") == 0) -+ return append_short_backtrace(result, pd, CD_TEXT_ATT_SIZE_BZ, print_item_name); -+ -+ /* Compat with previously-existed ad-hockery: %reporter */ -+ if (strcmp(item_name, "%reporter") == 0) -+ return append_text(result, "reporter", PACKAGE"-"VERSION, print_item_name); -+ -+ /* %oneline,%multiline,%text */ -+ bool oneline = (strcmp(item_name+1, "oneline" ) == 0); -+ bool multiline = (strcmp(item_name+1, "multiline") == 0); -+ bool text = (strcmp(item_name+1, "text" ) == 0); -+ if (!oneline && !multiline && !text) -+ { -+ log("Unknown or unsupported element specifier '%s'", item_name); -+ return 0; /* "I did not print anything" */ -+ } -+ -+ int printed = 0; -+ -+ /* Iterate over _sorted_ items */ -+ GList *sorted_names = g_hash_table_get_keys(pd); -+ sorted_names = g_list_sort(sorted_names, (GCompareFunc)strcmp); -+ -+ /* %text => do as if %oneline, then repeat as if %multiline */ -+ if (text) -+ oneline = 1; -+ -+ again: ; -+ GList *l = sorted_names; -+ while (l) -+ { -+ const char *name = l->data; -+ l = l->next; -+ struct problem_item *item = g_hash_table_lookup(pd, name); -+ if (!item) -+ continue; /* paranoia, won't happen */ -+ -+ if (!(item->flags & CD_FLAG_TXT)) -+ continue; -+ -+ if (is_explicit_or_forbidden(name, comment_fmt_spec)) -+ continue; -+ -+ char *formatted = problem_item_format(item); -+ char *content = formatted ? formatted : item->content; -+ char *eol = strchrnul(content, '\n'); -+ bool is_oneline = (eol[0] == '\0' || eol[1] == '\0'); -+ if (oneline == is_oneline) -+ printed |= append_text(result, name, content, print_item_name); -+ free(formatted); -+ } -+ if (text && oneline) -+ { -+ /* %text, and we just did %oneline. Repeat as if %multiline */ -+ oneline = 0; -+ /*multiline = 1; - not checked in fact, so why bother setting? */ -+ goto again; -+ } -+ -+ g_list_free(sorted_names); /* names themselves are not freed */ -+ -+ return printed; -+} -+ -+#define add_to_section_output(format, ...) \ -+ do { \ -+ for (; empty_lines > 0; --empty_lines) fputc('\n', result); \ -+ empty_lines = 0; \ -+ fprintf(result, format, __VA_ARGS__); \ -+ } while (0) -+ -+static void -+format_section(section_t *section, problem_data_t *pd, GList *comment_fmt_spec, FILE *result) -+{ -+ int empty_lines = -1; -+ -+ for (GList *iter = section->children; iter; iter = g_list_next(iter)) -+ { -+ section_t *child = (section_t *)iter->data; -+ if (child->items) -+ { -+ /* "Text: item[,item]..." */ -+ struct strbuf *output = strbuf_new(); -+ GList *item = child->items; -+ while (item) -+ { -+ const char *str = item->data; -+ item = item->next; -+ if (str[0] == '-') /* "-name", ignore it */ -+ continue; -+ append_item(output, str, pd, comment_fmt_spec); -+ } -+ -+ if (output->len != 0) -+ add_to_section_output((child->name[0] ? "%s:\n%s" : "%s%s"), -+ child->name, output->buf); -+ -+ strbuf_free(output); -+ } -+ else -+ { -+ /* Just "Text" (can be "") */ -+ -+ /* Filter out trailint empty lines */ -+ if (child->name[0] != '\0') -+ add_to_section_output("%s\n", child->name); -+ /* Do not count empty lines, if output wasn't yet produced */ -+ else if (empty_lines >= 0) -+ ++empty_lines; -+ } -+ } -+} -+ -+static GList * -+get_special_items(const char *item_name, problem_data_t *pd, GList *comment_fmt_spec) -+{ -+ /* %oneline,%multiline,%text,%binary */ -+ bool oneline = (strcmp(item_name+1, "oneline" ) == 0); -+ bool multiline = (strcmp(item_name+1, "multiline") == 0); -+ bool text = (strcmp(item_name+1, "text" ) == 0); -+ bool binary = (strcmp(item_name+1, "binary" ) == 0); -+ if (!oneline && !multiline && !text && !binary) -+ { -+ log("Unknown or unsupported element specifier '%s'", item_name); -+ return NULL; -+ } -+ -+ log_debug("Special item_name '%s', iterating for attach...", item_name); -+ GList *result = 0; -+ -+ /* Iterate over _sorted_ items */ -+ GList *sorted_names = g_hash_table_get_keys(pd); -+ sorted_names = g_list_sort(sorted_names, (GCompareFunc)strcmp); -+ -+ GList *l = sorted_names; -+ while (l) -+ { -+ const char *name = l->data; -+ l = l->next; -+ struct problem_item *item = g_hash_table_lookup(pd, name); -+ if (!item) -+ continue; /* paranoia, won't happen */ -+ -+ if (is_explicit_or_forbidden(name, comment_fmt_spec)) -+ continue; -+ -+ if ((item->flags & CD_FLAG_TXT) && !binary) -+ { -+ char *content = item->content; -+ char *eol = strchrnul(content, '\n'); -+ bool is_oneline = (eol[0] == '\0' || eol[1] == '\0'); -+ if (text || oneline == is_oneline) -+ result = g_list_append(result, xstrdup(name)); -+ } -+ else if ((item->flags & CD_FLAG_BIN) && binary) -+ result = g_list_append(result, xstrdup(name)); -+ } -+ -+ g_list_free(sorted_names); /* names themselves are not freed */ -+ -+ -+ log_debug("...Done iterating over '%s' for attach", item_name); -+ -+ return result; -+} -+ -+static GList * -+get_attached_files(problem_data_t *pd, GList *items, GList *comment_fmt_spec) -+{ -+ GList *result = NULL; -+ GList *item = items; -+ while (item != NULL) -+ { -+ const char *item_name = item->data; -+ item = item->next; -+ if (item_name[0] == '-') /* "-name", ignore it */ -+ continue; -+ -+ if (item_name[0] != '%') -+ { -+ result = g_list_append(result, xstrdup(item_name)); -+ continue; -+ } -+ -+ GList *special = get_special_items(item_name, pd, comment_fmt_spec); -+ if (special == NULL) -+ { -+ log_notice("No attachment found for '%s'", item_name); -+ continue; -+ } -+ -+ result = g_list_concat(result, special); -+ } -+ -+ return result; -+} -+ -+/* -+ * Problem Report - memor stream -+ * -+ * A wrapper for POSIX memory stream. -+ * -+ * A memory stream is presented as FILE *. -+ * -+ * A memory stream is associated with a pointer to written data and a pointer -+ * to size of the written data. -+ * -+ * This structure holds all of the used pointers. -+ */ -+struct memstream_buffer -+{ -+ char *msb_buffer; -+ size_t msb_size; -+ FILE *msb_stream; -+}; -+ -+static struct memstream_buffer * -+memstream_buffer_new() -+{ -+ struct memstream_buffer *self = xmalloc(sizeof(*self)); -+ -+ self->msb_buffer = NULL; -+ self->msb_stream = open_memstream(&(self->msb_buffer), &(self->msb_size)); -+ -+ return self; -+} -+ -+static void -+memstream_buffer_free(struct memstream_buffer *self) -+{ -+ if (self == NULL) -+ return; -+ -+ fclose(self->msb_stream); -+ self->msb_stream = DESTROYED_POINTER; -+ -+ free(self->msb_buffer); -+ self->msb_buffer = DESTROYED_POINTER; -+ -+ free(self); -+} -+ -+static FILE * -+memstream_get_stream(struct memstream_buffer *self) -+{ -+ assert(self != NULL); -+ -+ return self->msb_stream; -+} -+ -+static const char * -+memstream_get_string(struct memstream_buffer *self) -+{ -+ assert(self != NULL); -+ assert(self->msb_stream != NULL); -+ -+ fflush(self->msb_stream); -+ -+ return self->msb_buffer; -+} -+ -+ -+/* -+ * Problem Report -+ * -+ * The formated strings are internaly stored in "buffer"s. If a programer wants -+ * to get a formated section data, a getter function extracts those data from -+ * the apropriate buffer and returns them in form of null-terminated string. -+ * -+ * Each section has own buffer. -+ * -+ * There are three common sections that are always present: -+ * 1. summary -+ * 2. description -+ * 3. attach -+ * Buffers of these sections has own structure member for the sake of -+ * efficiency. -+ * -+ * The custom sections hash their buffers stored in a map where key is a -+ * section's name and value is a section's buffer. -+ * -+ * Problem report provides the programers with the possibility to ammend -+ * formated output to any section buffer. -+ */ -+struct problem_report -+{ -+ struct memstream_buffer *pr_sec_summ; ///< %summary buffer -+ struct memstream_buffer *pr_sec_desc; ///< %description buffer -+ GList *pr_attachments; ///< %attach - list of file names -+ GHashTable *pr_sec_custom; ///< map : %(custom section) -> buffer -+}; -+ -+static problem_report_t * -+problem_report_new() -+{ -+ problem_report_t *self = xmalloc(sizeof(*self)); -+ -+ self->pr_sec_summ = memstream_buffer_new(); -+ self->pr_sec_desc = memstream_buffer_new(); -+ self->pr_attachments = NULL; -+ self->pr_sec_custom = NULL; -+ -+ return self; -+} -+ -+static void -+problem_report_initialize_custom_sections(problem_report_t *self) -+{ -+ assert(self != NULL); -+ assert(self->pr_sec_custom == NULL); -+ -+ self->pr_sec_custom = g_hash_table_new_full(g_str_hash, g_str_equal, free, -+ (GDestroyNotify)memstream_buffer_free); -+} -+ -+static void -+problem_report_destroy_custom_sections(problem_report_t *self) -+{ -+ assert(self != NULL); -+ assert(self->pr_sec_custom != NULL); -+ -+ g_hash_table_destroy(self->pr_sec_custom); -+} -+ -+static int -+problem_report_add_custom_section(problem_report_t *self, const char *name) -+{ -+ assert(self != NULL); -+ -+ if (self->pr_sec_custom == NULL) -+ { -+ problem_report_initialize_custom_sections(self); -+ } -+ -+ if (problem_report_get_buffer(self, name)) -+ { -+ log_warning("Custom section already exists : '%s'", name); -+ return -EEXIST; -+ } -+ -+ log_debug("Problem report enriched with section : '%s'", name); -+ g_hash_table_insert(self->pr_sec_custom, xstrdup(name), memstream_buffer_new()); -+ return 0; -+} -+ -+static struct memstream_buffer * -+problem_report_get_section_buffer(const problem_report_t *self, const char *section_name) -+{ -+ if (self->pr_sec_custom == NULL) -+ { -+ log_debug("Couldn't find section '%s': no custom section added", section_name); -+ return NULL; -+ } -+ -+ return (struct memstream_buffer *)g_hash_table_lookup(self->pr_sec_custom, section_name); -+} -+ -+problem_report_buffer * -+problem_report_get_buffer(const problem_report_t *self, const char *section_name) -+{ -+ assert(self != NULL); -+ assert(section_name != NULL); -+ -+ if (strcmp(PR_SEC_SUMMARY, section_name) == 0) -+ return memstream_get_stream(self->pr_sec_summ); -+ -+ if (strcmp(PR_SEC_DESCRIPTION, section_name) == 0) -+ return memstream_get_stream(self->pr_sec_desc); -+ -+ struct memstream_buffer *buf = problem_report_get_section_buffer(self, section_name); -+ return buf == NULL ? NULL : memstream_get_stream(buf); -+} -+ -+const char * -+problem_report_get_summary(const problem_report_t *self) -+{ -+ assert(self != NULL); -+ -+ return memstream_get_string(self->pr_sec_summ); -+} -+ -+const char * -+problem_report_get_description(const problem_report_t *self) -+{ -+ assert(self != NULL); -+ -+ return memstream_get_string(self->pr_sec_desc); -+} -+ -+const char * -+problem_report_get_section(const problem_report_t *self, const char *section_name) -+{ -+ assert(self != NULL); -+ assert(section_name); -+ -+ struct memstream_buffer *buf = problem_report_get_section_buffer(self, section_name); -+ -+ if (buf == NULL) -+ return NULL; -+ -+ return memstream_get_string(buf); -+} -+ -+static void -+problem_report_set_attachments(problem_report_t *self, GList *attachments) -+{ -+ assert(self != NULL); -+ assert(self->pr_attachments == NULL); -+ -+ self->pr_attachments = attachments; -+} -+ -+GList * -+problem_report_get_attachments(const problem_report_t *self) -+{ -+ assert(self != NULL); -+ -+ return self->pr_attachments; -+} -+ -+void -+problem_report_free(problem_report_t *self) -+{ -+ if (self == NULL) -+ return; -+ -+ memstream_buffer_free(self->pr_sec_summ); -+ self->pr_sec_summ = DESTROYED_POINTER; -+ -+ memstream_buffer_free(self->pr_sec_desc); -+ self->pr_sec_desc = DESTROYED_POINTER; -+ -+ g_list_free_full(self->pr_attachments, free); -+ self->pr_attachments = DESTROYED_POINTER; -+ -+ if (self->pr_sec_custom) -+ { -+ problem_report_destroy_custom_sections(self); -+ self->pr_sec_custom = DESTROYED_POINTER; -+ } -+ -+ free(self); -+} -+ -+/* -+ * Problem Formatter - extra section -+ */ -+struct extra_section -+{ -+ char *pfes_name; ///< name with % prefix -+ int pfes_flags; ///< whether is required or not -+}; -+ -+static struct extra_section * -+extra_section_new(const char *name, int flags) -+{ -+ struct extra_section *self = xmalloc(sizeof(*self)); -+ -+ self->pfes_name = xstrdup(name); -+ self->pfes_flags = flags; -+ -+ return self; -+} -+ -+static void -+extra_section_free(struct extra_section *self) -+{ -+ if (self == NULL) -+ return; -+ -+ free(self->pfes_name); -+ self->pfes_name = DESTROYED_POINTER; -+ -+ free(self); -+} -+ -+static int -+extra_section_name_cmp(struct extra_section *lhs, const char *rhs) -+{ -+ return strcmp(lhs->pfes_name, rhs); -+} -+ -+/* -+ * Problem Formatter -+ * -+ * Holds parsed sections lists. -+ */ -+struct problem_formatter -+{ -+ GList *pf_sections; ///< parsed sections (struct section_t) -+ GList *pf_extra_sections; ///< user configured sections (struct extra_section) -+ char *pf_default_summary; ///< default summary format -+}; -+ -+problem_formatter_t * -+problem_formatter_new(void) -+{ -+ problem_formatter_t *self = xzalloc(sizeof(*self)); -+ -+ self->pf_default_summary = xstrdup("%reason%"); -+ -+ return self; -+} -+ -+void -+problem_formatter_free(problem_formatter_t *self) -+{ -+ if (self == NULL) -+ return; -+ -+ g_list_free_full(self->pf_sections, (GDestroyNotify)section_free); -+ self->pf_sections = DESTROYED_POINTER; -+ -+ g_list_free_full(self->pf_extra_sections, (GDestroyNotify)extra_section_free); -+ self->pf_extra_sections = DESTROYED_POINTER; -+ -+ free(self->pf_default_summary); -+ self->pf_default_summary = DESTROYED_POINTER; -+ -+ free(self); -+} -+ -+static int -+problem_formatter_is_section_known(problem_formatter_t *self, const char *name) -+{ -+ return strcmp(name, "summary") == 0 -+ || strcmp(name, "attach") == 0 -+ || strcmp(name, "description") == 0 -+ || NULL != g_list_find_custom(self->pf_extra_sections, name, (GCompareFunc)extra_section_name_cmp); -+} -+ -+// i.e additional_info -> no flags -+int -+problem_formatter_add_section(problem_formatter_t *self, const char *name, int flags) -+{ -+ /* Do not add already added sections */ -+ if (problem_formatter_is_section_known(self, name)) -+ { -+ log_debug("Extra section already exists : '%s' ", name); -+ return -EEXIST; -+ } -+ -+ self->pf_extra_sections = g_list_prepend(self->pf_extra_sections, -+ extra_section_new(name, flags)); -+ -+ return 0; -+} -+ -+// check format validity and produce warnings -+static int -+problem_formatter_validate(problem_formatter_t *self) -+{ -+ int retval = 0; -+ -+ /* Go through all (struct extra_section)s and check whete those having flag -+ * PFFF_REQUIRED are present in the parsed (struct section_t)s. -+ */ -+ for (GList *iter = self->pf_extra_sections; iter; iter = g_list_next(iter)) -+ { -+ struct extra_section *section = (struct extra_section *)iter->data; -+ -+ log_debug("Validating extra section : '%s'", section->pfes_name); -+ -+ if ( (PFFF_REQUIRED & section->pfes_flags) -+ && NULL == g_list_find_custom(self->pf_sections, section->pfes_name, (GCompareFunc)section_name_cmp)) -+ { -+ log_warning("Problem format misses required section : '%s'", section->pfes_name); -+ ++retval; -+ } -+ } -+ -+ /* Go through all the parsed (struct section_t)s check whether are all -+ * known, i.e. each section is either one of the common sections (summary, -+ * description, attach) or is present in the (struct extra_section)s. -+ */ -+ for (GList *iter = self->pf_sections; iter; iter = g_list_next(iter)) -+ { -+ section_t *section = (section_t *)iter->data; -+ -+ if (!problem_formatter_is_section_known(self, (section->name + 1))) -+ { -+ log_warning("Problem format contains unrecognized section : '%s'", section->name); -+ ++retval; -+ } -+ } -+ -+ return retval; -+} -+ -+int -+problem_formatter_load_string(problem_formatter_t *self, const char *fmt) -+{ -+ const size_t len = strlen(fmt); -+ if (len != 0) -+ { -+ FILE *fp = fmemopen((void *)fmt, len, "r"); -+ if (fp == NULL) -+ { -+ error_msg("Not enough memory to open a stream for reading format string."); -+ return -ENOMEM; -+ } -+ -+ self->pf_sections = load_stream(fp); -+ fclose(fp); -+ } -+ -+ return problem_formatter_validate(self); -+} -+ -+int -+problem_formatter_load_file(problem_formatter_t *self, const char *path) -+{ -+ FILE *fp = stdin; -+ if (strcmp(path, "-") != 0) -+ { -+ fp = fopen(path, "r"); -+ if (!fp) -+ return -ENOENT; -+ } -+ -+ self->pf_sections = load_stream(fp); -+ -+ if (fp != stdin) -+ fclose(fp); -+ -+ return problem_formatter_validate(self); -+} -+ -+// generates report -+int -+problem_formatter_generate_report(const problem_formatter_t *self, problem_data_t *data, problem_report_t **report) -+{ -+ problem_report_t *pr = problem_report_new(); -+ -+ for (GList *iter = self->pf_extra_sections; iter; iter = g_list_next(iter)) -+ problem_report_add_custom_section(pr, ((struct extra_section *)iter->data)->pfes_name); -+ -+ bool has_summary = false; -+ for (GList *iter = self->pf_sections; iter; iter = g_list_next(iter)) -+ { -+ section_t *section = (section_t *)iter->data; -+ -+ /* %summary is something special */ -+ if (strcmp(section->name, "%summary") == 0) -+ { -+ has_summary = true; -+ format_percented_string((const char *)section->items->data, data, -+ problem_report_get_buffer(pr, PR_SEC_SUMMARY)); -+ } -+ /* %attach as well */ -+ else if (strcmp(section->name, "%attach") == 0) -+ { -+ problem_report_set_attachments(pr, get_attached_files(data, section->items, self->pf_sections)); -+ } -+ else /* %description or a custom section (e.g. %additional_info) */ -+ { -+ FILE *buffer = problem_report_get_buffer(pr, section->name + 1); -+ -+ if (buffer != NULL) -+ { -+ log_debug("Formatting section : '%s'", section->name); -+ format_section(section, data, self->pf_sections, buffer); -+ } -+ else -+ log_warning("Unsupported section '%s'", section->name); -+ } -+ } -+ -+ if (!has_summary) { -+ log_debug("Problem format misses section '%%summary'. Using the default one : '%s'.", -+ self->pf_default_summary); -+ -+ format_percented_string(self->pf_default_summary, -+ data, problem_report_get_buffer(pr, PR_SEC_SUMMARY)); -+ } -+ -+ *report = pr; -+ return 0; -+} -diff --git a/tests/Makefile.am b/tests/Makefile.am -index a680f05..f6c5fee 100644 ---- a/tests/Makefile.am -+++ b/tests/Makefile.am -@@ -42,7 +42,8 @@ TESTSUITE_AT = \ - report_python.at \ - xfuncs.at \ - string_list.at \ -- ureport.at -+ ureport.at \ -+ problem_report.at - - EXTRA_DIST += $(TESTSUITE_AT) - TESTSUITE = $(srcdir)/testsuite -diff --git a/tests/problem_report.at b/tests/problem_report.at -new file mode 100644 -index 0000000..8bc469f ---- /dev/null -+++ b/tests/problem_report.at -@@ -0,0 +1,690 @@ -+# -*- Autotest -*- -+ -+AT_BANNER([problem report]) -+ -+## ------- ## -+## summary ## -+## ------- ## -+ -+AT_TESTFUN([summary], -+[[ -+#include "problem_report.h" -+#include "internal_libreport.h" -+#include -+ -+int main(int argc, char **argv) -+{ -+ const char *const test_format_result[][2] = { -+ { -+ "%summary:: [abrt] trivial string", -+ "[abrt] trivial string" -+ }, -+ -+ { -+ "%summary:: [abrt] %package%", -+ "[abrt] libreport" -+ }, -+ -+ { -+ "%summary:: [abrt] %package%[[ : %crash_function%()]][[ : %does_not_exist%]][[ : %reason%]][[ TAINTED: %taint_flags%]]", -+ "[abrt] libreport : run_event() : Killed by SIGSEGV" -+ }, -+ -+ { -+ "%summary:: [abrt] %package%[[ : %crash_function%[[ : %does_not_exist%]]()]][[ : %reason%]]", -+ "[abrt] libreport : run_event() : Killed by SIGSEGV" -+ }, -+ -+ { -+ "%summary:: [abrt] %package%[[ : %does_not_exist%[[ : %crash_function%()]]]][[ : %reason%]]", -+ "[abrt] libreport : Killed by SIGSEGV" -+ }, -+ -+ { -+ "%summary:: [[%does_not_exist%]][[%once_more%]][abrt] %package%", -+ "[abrt] libreport" -+ }, -+ -+ { -+ "", -+ "Killed by SIGSEGV" -+ }, -+ }; -+ -+ g_verbose = 3; -+ -+ problem_data_t *data = problem_data_new(); -+ problem_data_add_text_noteditable(data, "package", "libreport"); -+ problem_data_add_text_noteditable(data, "crash_function", "run_event"); -+ problem_data_add_text_noteditable(data, "reason", "Killed by SIGSEGV"); -+ -+ for (size_t i = 0; i < sizeof(test_format_result)/sizeof(*test_format_result); ++i) { -+ problem_formatter_t *pf = problem_formatter_new(); -+ assert(!problem_formatter_load_string(pf, test_format_result[i][0])); -+ -+ problem_report_t *pr = NULL; -+ assert(!problem_formatter_generate_report(pf, data, &pr)); -+ assert(pr != NULL); -+ -+ const char *summary = problem_report_get_summary(pr); -+ assert(summary != NULL); -+ -+ fprintf(stderr, "expected: '%s'\n", test_format_result[i][1]); -+ fprintf(stderr, "result : '%s'\n", summary); -+ -+ assert(strcmp(test_format_result[i][1], summary) == 0); -+ -+ problem_report_free(pr); -+ problem_formatter_free(pf); -+ } -+ -+ problem_data_free(data); -+ -+ return 0; -+} -+]]) -+ -+## ---------- ## -+## desciption ## -+## ---------- ## -+ -+AT_TESTFUN([description], -+[[ -+#include "problem_report.h" -+#include "internal_libreport.h" -+#include -+ -+int main(int argc, char **argv) -+{ -+ const char *const test_format_result[][2] = { -+ { -+ "\n"\ -+ "\n"\ -+ "\n"\ -+ "Single line\n" -+ "\n"\ -+ "\n"\ -+ "\n", -+ -+ "Single line\n" -+ }, -+ -+ { -+ "\n"\ -+ "\n"\ -+ "\n"\ -+ "Comment:: %bare_comment" -+ "\n"\ -+ "\n"\ -+ "\n"\ -+ "Ad-hoc line\n" -+ "\n"\ -+ "\n"\ -+ "\n"\ -+ "Additional:: package,\\\n" -+ "uuid,cwd\\\\" -+ ",user_name" -+ "\n"\ -+ "\n", -+ -+ "Comment:\n" \ -+ "Hello, world!\n" -+ "\n"\ -+ "\n"\ -+ "Ad-hoc line\n" -+ "\n"\ -+ "\n"\ -+ "\n"\ -+ "Additional:\n"\ -+ "package: libreport\n"\ -+ "uuid: 123456789ABCDEF\n"\ -+ "user_name: abrt\n", -+ }, -+ -+ { -+ ":: %bare_description", -+ -+ "I run will_segfault and\n"\ -+ "it crashed as expected\n" -+ }, -+ -+ { -+ "User:: %bare_user_name,uid\n"\ -+ "Additional info:: -uuid,%oneline,-comment,-package", -+ -+ "User:\n"\ -+ "abrt\n"\ -+ "uid: 69\n"\ -+ "Additional info:\n"\ -+ "analyzer: CCpp\n"\ -+ "root: /var/run/mock/abrt\n"\ -+ "type: CCpp\n" -+ }, -+ -+ { -+ ":: %reporter", -+ NULL /* do no check results*/ -+ }, -+ -+ { -+ "Truncated backtrace:: %bare_%short_backtrace", -+ -+ "Truncated backtrace:\n" -+ "Thread no. 0 (7 frames)\n" -+ " #1 crash at will_segfault.c:19\n" -+ " #2 varargs at will_segfault.c:31\n" -+ " #3 inlined at will_segfault.c:40\n" -+ " #4 f at will_segfault.c:45\n" -+ " #5 callback at will_segfault.c:50\n" -+ " #6 call_me_back at libwillcrash.c:8\n" -+ " #7 recursive at will_segfault.c:59\n" -+ }, -+ }; -+ -+ g_verbose = 3; -+ -+ problem_data_t *data = problem_data_new(); -+ problem_data_add_text_noteditable(data, "package", "libreport"); -+ problem_data_add_text_noteditable(data, "analyzer", "CCpp"); -+ problem_data_add_text_noteditable(data, "type", "CCpp"); -+ problem_data_add_text_noteditable(data, "comment", "Hello, world!"); -+ problem_data_add_text_noteditable(data, "uuid", "123456789ABCDEF"); -+ problem_data_add_text_noteditable(data, "uid", "69"); -+ problem_data_add_text_noteditable(data, "user_name", "abrt"); -+ problem_data_add_text_noteditable(data, "root", "/var/run/mock/abrt"); -+ problem_data_add_text_noteditable(data, "description", "I run will_segfault and\nit crashed as expected\n"); -+ problem_data_add_text_noteditable(data, "backtrace", -+"Thread 1 (LWP 11865):\n"\ -+"#0 printf (__fmt=0x400acf \"Result: %d\\n\") at /usr/include/bits/stdio2.h:104\n"\ -+"No locals.\n"\ -+"#1 crash (p=p@entry=0x0) at will_segfault.c:19\n"\ -+"i = \n"\ -+"#2 0x0000000000400964 in varargs (num_args=1, num_args@entry=2) at will_segfault.c:31\n"\ -+"p = \n"\ -+"ap = {{gp_offset = 24, fp_offset = 32767, overflow_arg_area = 0x7fff4fc8a0c0, reg_save_area = 0x7fff4fc8a080}}\n"\ -+"#3 0x00000000004009be in inlined (p=0x0) at will_segfault.c:40\n"\ -+"num = 42\n"\ -+"#4 f (p=p@entry=0x0) at will_segfault.c:45\n"\ -+"No locals.\n"\ -+"#5 0x00000000004009e9 in callback (data=data@entry=0x0) at will_segfault.c:50\n"\ -+"No locals.\n"\ -+"#6 0x00000032f76006f9 in call_me_back (cb=cb@entry=0x4009e0 , data=data@entry=0x0) at libwillcrash.c:8\n"\ -+"res = \n"\ -+"#7 0x0000000000400a14 in recursive (i=i@entry=0) at will_segfault.c:59\n"\ -+"p = \n"\ -+"#8 0x0000000000400a00 in recursive (i=i@entry=1) at will_segfault.c:66\n"\ -+"No locals.\n"\ -+"#9 0x0000000000400a00 in recursive (i=i@entry=2) at will_segfault.c:66\n"\ -+"No locals.\n"\ -+"#10 0x0000000000400775 in main (argc=, argv=) at will_segfault.c:83\n"\ -+"No locals.\n"\ -+"From To Syms Read Shared Object Library\n"\ -+"0x00000032f76005f0 0x00000032f7600712 Yes /lib64/libwillcrash.so.0\n"\ -+"0x0000003245c1f4f0 0x0000003245d6aca4 Yes /lib64/libc.so.6\n"\ -+"0x0000003245800b10 0x000000324581b6d0 Yes /lib64/ld-linux-x86-64.so.2\n"\ -+"$1 = 0x0\n" -+"No symbol \"__glib_assert_msg\" in current context.\n"\ -+"rax 0xf 15\n"\ -+"rbx 0x0 0\n"\ -+"rcx 0x7ff96f752000 140709293531136\n"\ -+"rdx 0x3245fb9a40 215922481728\n"\ -+"rsi 0x7ff96f752000 140709293531136\n"\ -+"rdi 0x1 1\n"\ -+"rbp 0x400a30 0x400a30 <__libc_csu_init>\n"\ -+"rsp 0x7fff4fc8a050 0x7fff4fc8a050\n"\ -+"r8 0xffffffff 4294967295\n"\ -+"r9 0x0 0\n"\ -+"r10 0x22 34\n"\ -+"r11 0x246 582\n"\ -+"r12 0x40079f 4196255\n"\ -+"r13 0x7fff4fc8a210 140734531936784\n"\ -+"r14 0x0 0\n"\ -+"r15 0x0 0\n"\ -+"rip 0x4008ae 0x4008ae \n"\ -+"eflags 0x10246 [ PF ZF IF RF ]\n"\ -+"cs 0x33 51\n"\ -+"ss 0x2b 43\n"\ -+"ds 0x0 0\n"\ -+"es 0x0 0\n"\ -+"fs 0x0 0\n"\ -+"gs 0x0 0\n"\ -+"st0 0 (raw 0x00000000000000000000)\n"\ -+"st1 0 (raw 0x00000000000000000000)\n"\ -+"st2 0 (raw 0x00000000000000000000)\n"\ -+"st3 0 (raw 0x00000000000000000000)\n"\ -+"st4 0 (raw 0x00000000000000000000)\n"\ -+"st5 0 (raw 0x00000000000000000000)\n"\ -+"st6 0 (raw 0x00000000000000000000)\n"\ -+"st7 0 (raw 0x00000000000000000000)\n"\ -+"fctrl 0x37f 895\n"\ -+"fstat 0x0 0\n"\ -+"ftag 0xffff 65535\n"\ -+"fiseg 0x0 0\n"\ -+"fioff 0x0 0\n"\ -+"foseg 0x0 0\n"\ -+"fooff 0x0 0\n"\ -+"fop 0x0 0\n"\ -+"xmm0 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 }, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\ -+"xmm1 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x2f }, v8_int16 = {0x2f2f, 0x2f2f, 0x2f2f, 0x2f2f, 0x2f2f, 0x2f2f, 0x2f2f, 0x2f2f}, v4_int32 = {0x2f2f2f2f, 0x2f2f2f2f, 0x2f2f2f2f, 0x2f2f2f2f}, v2_int64 = {0x2f2f2f2f2f2f2f2f, 0x2f2f2f2f2f2f2f2f}, uint128 = 0x2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f}\n"\ -+"xmm2 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 }, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\ -+"xmm3 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 , 0xff, 0x0, 0x0}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff00, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0xff00}, v2_int64 = {0x0, 0xff0000000000}, uint128 = 0x0000ff00000000000000000000000000}\n"\ -+"xmm4 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0xff0000, 0x0}, v2_int64 = {0x0, 0xff0000}, uint128 = 0x0000000000ff00000000000000000000}\n"\ -+"xmm5 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 }, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\ -+"xmm6 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 }, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\ -+"xmm7 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 }, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\ -+"xmm8 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 }, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\ -+"xmm9 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 }, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\ -+"xmm10 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 }, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\ -+"xmm11 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 }, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\ -+"xmm12 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 , 0xff, 0x0}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff}, v4_int32 = {0x0, 0x0, 0x0, 0xff0000}, v2_int64 = {0x0, 0xff000000000000}, uint128 = 0x00ff0000000000000000000000000000}\n"\ -+"xmm13 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 }, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\ -+"xmm14 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 }, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\ -+"xmm15 {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 }, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\ -+"mxcsr 0x1f80 [ IM DM ZM OM UM PM ]\n"\ -+"Dump of assembler code for function crash:\n"\ -+" 0x00000000004008a0 <+0>: push %rbx\n"\ -+" 0x00000000004008a1 <+1>: mov %rdi,%rbx\n"\ -+" 0x00000000004008a4 <+4>: mov $0x400ac0,%edi\n"\ -+" 0x00000000004008a9 <+9>: callq 0x4006f0 \n"\ -+" => 0x00000000004008ae <+14>: mov (%rbx),%edx\n"\ -+" 0x00000000004008b0 <+16>: mov $0x400acf,%esi\n"\ -+" 0x00000000004008b5 <+21>: mov $0x1,%edi\n"\ -+" 0x00000000004008ba <+26>: xor %eax,%eax\n"\ -+" 0x00000000004008bc <+28>: callq 0x400740 <__printf_chk@plt>\n"\ -+" 0x00000000004008c1 <+33>: pop %rbx\n"\ -+" 0x00000000004008c2 <+34>: retq\n"\ -+"End of assembler dump.\n" -+ ); -+ -+ for (size_t i = 0; i < sizeof(test_format_result)/sizeof(*test_format_result); ++i) { -+ problem_formatter_t *pf = problem_formatter_new(); -+ assert(!problem_formatter_load_string(pf, test_format_result[i][0])); -+ -+ problem_report_t *pr = NULL; -+ assert(!problem_formatter_generate_report(pf, data, &pr)); -+ assert(pr != NULL); -+ -+ const char *summary = problem_report_get_description(pr); -+ assert(summary != NULL); -+ -+ if (test_format_result[i][1] != NULL) { -+ fprintf(stderr, "####\n"); -+ fprintf(stderr, "expected: '%s'\n", test_format_result[i][1]); -+ fprintf(stderr, "====\n"); -+ fprintf(stderr, "result : '%s'\n", summary); -+ fprintf(stderr, "####\n"); -+ -+ assert(strcmp(test_format_result[i][1], summary) == 0); -+ } -+ -+ problem_report_free(pr); -+ problem_formatter_free(pf); -+ } -+ -+ problem_data_free(data); -+ -+ return 0; -+} -+]]) -+ -+## ------ ## -+## attach ## -+## ------ ## -+ -+AT_TESTFUN([attach], -+[[ -+#include "problem_report.h" -+#include "internal_libreport.h" -+#include -+ -+int main(int argc, char **argv) -+{ -+ g_verbose = 3; -+ -+ const char *fst[] = { "backtrace", "screenshot", "description", NULL }; -+ -+ struct test_case { -+ const char *format; -+ const char *const *files; -+ } cases [] = { -+ { -+ .format = "%attach:: %multiline,%binary,-coredump", -+ .files = fst, -+ } -+ }; -+ -+ problem_data_t *data = problem_data_new(); -+ problem_data_add_text_noteditable(data, "package", "libreport"); -+ problem_data_add_text_noteditable(data, "analyzer", "CCpp"); -+ problem_data_add_text_noteditable(data, "type", "CCpp"); -+ problem_data_add_text_noteditable(data, "comment", "Hello, world!"); -+ problem_data_add_text_noteditable(data, "uuid", "123456789ABCDEF"); -+ problem_data_add_text_noteditable(data, "uid", "69"); -+ problem_data_add_text_noteditable(data, "user_name", "abrt"); -+ problem_data_add_text_noteditable(data, "root", "/var/run/mock/abrt"); -+ problem_data_add_text_noteditable(data, "description", "I run will_segfault and\nit crashed as expected\n"); -+ problem_data_add_file(data, "coredump", "/what/ever/path/to/coredump"); -+ problem_data_add_file(data, "screenshot", "/what/ever/path/to/screenshot"); -+ problem_data_add_text_noteditable(data, "backtrace", -+"Thread 1 (LWP 11865):\n"\ -+"#0 printf (__fmt=0x400acf \"Result: %d\\n\") at /usr/include/bits/stdio2.h:104\n"\ -+"No locals.\n"\ -+"#1 crash (p=p@entry=0x0) at will_segfault.c:19\n"\ -+"i = \n"\ -+"#2 0x0000000000400964 in varargs (num_args=1, num_args@entry=2) at will_segfault.c:31\n"\ -+"p = \n"\ -+"ap = {{gp_offset = 24, fp_offset = 32767, overflow_arg_area = 0x7fff4fc8a0c0, reg_save_area = 0x7fff4fc8a080}}\n"\ -+"#3 0x00000000004009be in inlined (p=0x0) at will_segfault.c:40\n"\ -+"num = 42\n"\ -+"#4 f (p=p@entry=0x0) at will_segfault.c:45\n"\ -+"No locals.\n"\ -+"#5 0x00000000004009e9 in callback (data=data@entry=0x0) at will_segfault.c:50\n"\ -+"No locals.\n"\ -+"#6 0x00000032f76006f9 in call_me_back (cb=cb@entry=0x4009e0 , data=data@entry=0x0) at libwillcrash.c:8\n"\ -+"res = \n"\ -+"#7 0x0000000000400a14 in recursive (i=i@entry=0) at will_segfault.c:59\n"\ -+"p = \n"\ -+"#8 0x0000000000400a00 in recursive (i=i@entry=1) at will_segfault.c:66\n"\ -+"No locals.\n"\ -+"#9 0x0000000000400a00 in recursive (i=i@entry=2) at will_segfault.c:66\n"\ -+"No locals.\n"\ -+"#10 0x0000000000400775 in main (argc=, argv=) at will_segfault.c:83\n"\ -+"No locals.\n"); -+ -+ for (size_t i = 0; i < sizeof(cases)/sizeof(*cases); ++i) { -+ fprintf(stderr, "%s", cases[i].format); -+ -+ problem_formatter_t *pf = problem_formatter_new(); -+ assert(!problem_formatter_load_string(pf, cases[i].format)); -+ -+ problem_report_t *pr = NULL; -+ assert(!problem_formatter_generate_report(pf, data, &pr)); -+ assert(pr != NULL); -+ -+ const char *const *iter = cases[i].files; -+ -+ if (*iter != NULL) { -+ assert(problem_report_get_attachments(pr)); -+ } -+ -+ GList *clone = g_list_copy_deep(problem_report_get_attachments(pr), (GCopyFunc)xstrdup, NULL); -+ -+ while (*iter) { -+ GList *item = g_list_find_custom(clone, *iter, (GCompareFunc)strcmp); -+ -+ if (item == NULL) { -+ fprintf(stderr, "format: '%s'\n", cases[i].format); -+ fprintf(stderr, "missing file: '%s'\n", *iter); -+ abort(); -+ } -+ else { -+ free(item->data); -+ clone = g_list_delete_link(clone, item); -+ } -+ -+ ++iter; -+ } -+ -+ if (clone != NULL) { -+ for (GList *iter = clone; iter; iter = g_list_next(iter)) { -+ fprintf(stderr, "extra : '%s'\n", (const char *)iter->data); -+ } -+ abort(); -+ } -+ -+ problem_report_free(pr); -+ problem_formatter_free(pf); -+ } -+ -+ problem_data_free(data); -+ -+ return 0; -+} -+]]) -+ -+ -+## -------------- ## -+## custom_section ## -+## -------------- ## -+ -+AT_TESTFUN([custom_section], -+[[ -+#include "problem_report.h" -+#include "internal_libreport.h" -+#include -+ -+int main(int argc, char **argv) -+{ -+ g_verbose = 3; -+ -+ struct test_case; -+ struct test_case { -+ const char *section_name; -+ int section_flags; -+ int retval_load; -+ int retval_generate; -+ const char *format; -+ const char *output; -+ } cases [] = { -+ { .section_name = NULL, -+ .section_flags = 0, -+ .retval_load = 1, -+ .retval_generate = 0, -+ .format = -+ "%additional_info::\n" -+ ":: package,\\\n" -+ "uuid,cwd\\\\" -+ ",user_name" -+ , -+ .output = NULL, -+ }, -+ -+ { .section_name = "additional_info", -+ .section_flags = 0, -+ .retval_load = 0, -+ .retval_generate = 0, -+ .format = -+ "%additional_info::\n" -+ ":: package,\\\n" -+ "uuid,cwd\\\\" -+ ",user_name" -+ , -+ .output = -+ "package: libreport\n" -+ "uuid: 0123456789ABCDEF\n" -+ "user_name: abrt\n" -+ , -+ }, -+ -+ { .section_name = "additional_info", -+ .section_flags = PFFF_REQUIRED, -+ .retval_load = 1, -+ .retval_generate = 0, -+ .format = "%summary:: [abrt] %package%\n:: %reporter\n", -+ .output = NULL, -+ }, -+ -+ { .section_name = "additional_info", -+ .section_flags = 0, -+ .retval_load = 0, -+ .retval_generate = 0, -+ .format = "%summary:: [abrt] %package%\n:: %reporter\n", -+ .output = "", -+ }, -+ -+ { .section_name = "additional_info", -+ .section_flags = 0, -+ .retval_load = 0, -+ .retval_generate = 0, -+ .format = -+ "%additional_info:: root\n" -+ "Info:: package,\\\n" -+ "uuid,cwd\\\\" -+ ",user_name" -+ , -+ .output = -+ "Info:\n" -+ "package: libreport\n" -+ "uuid: 0123456789ABCDEF\n" -+ "user_name: abrt\n" -+ , -+ }, -+ }; -+ -+ problem_data_t *data = problem_data_new(); -+ problem_data_add_text_noteditable(data, "package", "libreport"); -+ problem_data_add_text_noteditable(data, "crash_function", "run_event"); -+ problem_data_add_text_noteditable(data, "reason", "Killed by SIGSEGV"); -+ problem_data_add_text_noteditable(data, "uuid", "0123456789ABCDEF"); -+ problem_data_add_text_noteditable(data, "user_name", "abrt"); -+ -+ for (size_t i = 0; i < sizeof(cases)/sizeof(*cases); ++i) { -+ fprintf(stderr, "#### Test case %zd ####\n", i + 1); -+ -+ struct test_case *tcase = cases + i; -+ problem_formatter_t *pf = problem_formatter_new(); -+ -+ if (tcase->section_name != NULL) { -+ problem_formatter_add_section(pf, tcase->section_name, tcase->section_flags); -+ } -+ -+ int r = problem_formatter_load_string(pf, tcase->format); -+ if (r != tcase->retval_load) { -+ fprintf(stderr, "Load : expected : %d , got : %d\n", tcase->retval_load, r); -+ abort(); -+ } -+ -+ if (tcase->retval_load != 0) { -+ goto next_test_case; -+ } -+ -+ problem_report_t *pr = NULL; -+ r = problem_formatter_generate_report(pf, data, &pr); -+ if (r != tcase->retval_generate) { -+ fprintf(stderr, "Generaet : expected : %d , got : %d\n", tcase->retval_generate, r); -+ abort(); -+ } -+ -+ if (tcase->retval_generate != 0) { -+ goto next_test_case; -+ } -+ -+ const char *output = problem_report_get_section(pr, tcase->section_name); -+ assert(output); -+ -+ if (strcmp(output, tcase->output) != 0) { -+ fprintf(stderr, "expected:\n'%s'\n", tcase->output); -+ fprintf(stderr, "result :\n'%s'\n", output); -+ abort(); -+ } -+ -+ problem_report_free(pr); -+ -+next_test_case: -+ problem_formatter_free(pf); -+ fprintf(stderr, "#### Finished - Test case %zd ####\n", i + 1); -+ } -+ -+ problem_data_free(data); -+ -+ return 0; -+} -+]]) -+ -+## ------ ## -+## sanity ## -+## ------ ## -+ -+AT_TESTFUN([sanity], -+[[ -+#include "problem_report.h" -+#include "internal_libreport.h" -+#include -+#include -+ -+void assert_equal_strings(const char *res, const char *exp) -+{ -+ if ( (res == NULL && exp != NULL) -+ || (res != NULL && exp == NULL) -+ || ((res != NULL && exp != NULL) && strcmp(res, exp) != 0) -+ ) { -+ fprintf(stderr, "expected : '%s'\n", exp); -+ fprintf(stderr, "result : '%s'\n", res); -+ abort(); -+ } -+} -+ -+int main(int argc, char **argv) -+{ -+ g_verbose = 3; -+ -+ problem_formatter_t *pf = problem_formatter_new(); -+ -+ assert(problem_formatter_add_section(pf, "summary", 0) == -EEXIST); -+ assert(problem_formatter_add_section(pf, "description", 0) == -EEXIST); -+ assert(problem_formatter_add_section(pf, "attach", 0) == -EEXIST); -+ -+ assert(problem_formatter_add_section(pf, "additional_info", 0) == 0); -+ assert(problem_formatter_add_section(pf, "additional_info", 0) == -EEXIST); -+ -+ problem_data_t *data = problem_data_new(); -+ problem_data_add_text_noteditable(data, "package", "libreport"); -+ problem_data_add_text_noteditable(data, "crash_function", "run_event"); -+ problem_data_add_text_noteditable(data, "reason", "Killed by SIGSEGV"); -+ problem_data_add_text_noteditable(data, "comment", "Hello, world!"); -+ problem_data_add_text_noteditable(data, "root", "/var/run/mock/abrt"); -+ problem_data_add_text_noteditable(data, "user_name", "abrt"); -+ problem_data_add_file(data, "screenshot", "/what/ever/path/to/screenshot"); -+ -+ problem_formatter_load_string(pf, -+ "%summary:: [abrt] %package% : %reason%\n\n" -+ "Description:: %bare_comment\n\n" -+ "%attach:: screenshot\n\n" -+ "%additional_info::\n\n" -+ "User:: root,user_name,uid\n\n\n" -+ ); -+ -+ problem_report_t *pr = NULL; -+ problem_formatter_generate_report(pf, data, &pr); -+ -+ assert_equal_strings(problem_report_get_summary(pr), -+ "[abrt] libreport : Killed by SIGSEGV"); -+ -+ problem_report_buffer_printf(problem_report_get_buffer(pr, PR_SEC_SUMMARY), " - test"); -+ -+ assert_equal_strings(problem_report_get_summary(pr), -+ "[abrt] libreport : Killed by SIGSEGV - test"); -+ -+ -+ assert_equal_strings(problem_report_get_description(pr), -+ "Description:\nHello, world!\n"); -+ -+ problem_report_buffer_printf(problem_report_get_buffer(pr, PR_SEC_DESCRIPTION), "Test line\n"); -+ -+ assert_equal_strings(problem_report_get_description(pr), -+ "Description:\nHello, world!\nTest line\n"); -+ -+ -+ assert_equal_strings(problem_report_get_section(pr, "additional_info"), -+ "User:\n" -+ "root: /var/run/mock/abrt\n" -+ "user_name: abrt\n" -+ ); -+ -+ problem_report_buffer_printf(problem_report_get_buffer(pr, "additional_info"), "uid: 42\n"); -+ -+ assert_equal_strings(problem_report_get_section(pr, "additional_info"), -+ "User:\n" -+ "root: /var/run/mock/abrt\n" -+ "user_name: abrt\n" -+ "uid: 42\n" -+ ); -+ -+ -+ problem_report_free(pr); -+ problem_data_free(data); -+ problem_formatter_free(pf); -+ -+ return 0; -+} -+]]) -diff --git a/tests/testsuite.at b/tests/testsuite.at -index abad32b..340c758 100644 ---- a/tests/testsuite.at -+++ b/tests/testsuite.at -@@ -17,3 +17,4 @@ m4_include([xml_definition.at]) - m4_include([report_python.at]) - m4_include([string_list.at]) - m4_include([ureport.at]) -+m4_include([problem_report.at]) --- -1.8.3.1 - diff --git a/SOURCES/0128-bugzilla-port-to-Problem-Format-API.patch b/SOURCES/0128-bugzilla-port-to-Problem-Format-API.patch deleted file mode 100644 index e6f9bbe..0000000 --- a/SOURCES/0128-bugzilla-port-to-Problem-Format-API.patch +++ /dev/null @@ -1,781 +0,0 @@ -From 4dd20b271a2e0bf7497654cec97541898b3bae31 Mon Sep 17 00:00:00 2001 -From: Jakub Filak -Date: Thu, 4 Dec 2014 08:45:07 +0100 -Subject: [PATCH 128/136] bugzilla: port to Problem Format API - -Related to #303 - -Signed-off-by: Jakub Filak ---- - src/plugins/reporter-bugzilla.c | 691 ++++------------------------------------ - 1 file changed, 59 insertions(+), 632 deletions(-) - -diff --git a/src/plugins/reporter-bugzilla.c b/src/plugins/reporter-bugzilla.c -index 097924e..38f48ef 100644 ---- a/src/plugins/reporter-bugzilla.c -+++ b/src/plugins/reporter-bugzilla.c -@@ -17,515 +17,11 @@ - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - #include "internal_libreport.h" -+#include "problem_report.h" - #include "client.h" - #include "abrt_xmlrpc.h" - #include "rhbz.h" - --#include --#include -- --struct section_t { -- char *name; -- GList *items; --}; --typedef struct section_t section_t; -- -- --/* Utility functions */ -- --static --GList* split_string_on_char(const char *str, char ch) --{ -- GList *list = NULL; -- for (;;) -- { -- const char *delim = strchrnul(str, ch); -- list = g_list_prepend(list, xstrndup(str, delim - str)); -- if (*delim == '\0') -- break; -- str = delim + 1; -- } -- return g_list_reverse(list); --} -- --static --int compare_item_name(const char *lookup, const char *name) --{ -- if (lookup[0] == '-') -- lookup++; -- else if (strncmp(lookup, "%bare_", 6) == 0) -- lookup += 6; -- return strcmp(lookup, name); --} -- --static --int is_item_name_in_section(const section_t *lookup, const char *name) --{ -- if (g_list_find_custom(lookup->items, name, (GCompareFunc)compare_item_name)) -- return 0; /* "found it!" */ -- return 1; --} -- --static --bool is_explicit_or_forbidden(const char *name, GList *comment_fmt_spec) --{ -- return g_list_find_custom(comment_fmt_spec, name, (GCompareFunc)is_item_name_in_section); --} -- --static --GList* load_bzrep_conf_file(const char *path) --{ -- FILE *fp = stdin; -- if (strcmp(path, "-") != 0) -- { -- fp = fopen(path, "r"); -- if (!fp) -- return NULL; -- } -- -- GList *sections = NULL; -- -- char *line; -- while ((line = xmalloc_fgetline(fp)) != NULL) -- { -- /* Skip comments */ -- char first = *skip_whitespace(line); -- if (first == '#') -- goto free_line; -- -- /* Handle trailing backslash continuation */ -- check_continuation: ; -- unsigned len = strlen(line); -- if (len && line[len-1] == '\\') -- { -- line[len-1] = '\0'; -- char *next_line = xmalloc_fgetline(fp); -- if (next_line) -- { -- line = append_to_malloced_string(line, next_line); -- free(next_line); -- goto check_continuation; -- } -- } -- -- /* We are reusing line buffer to form temporary -- * "key\0values\0..." in its beginning -- */ -- bool summary_line = false; -- char *value = NULL; -- char *src; -- char *dst; -- for (src = dst = line; *src; src++) -- { -- char c = *src; -- /* did we reach the value list? */ -- if (!value && c == ':' && src[1] == ':') -- { -- *dst++ = '\0'; /* terminate key */ -- src += 2; -- value = dst; /* remember where value starts */ -- summary_line = (strcmp(line, "%summary") == 0); -- if (summary_line) -- { -- value = src; -- break; -- } -- continue; -- } -- /* skip whitespace in value list */ -- if (value && isspace(c)) -- continue; -- *dst++ = c; /* store next key or value char */ -- } -- -- GList *item_list = NULL; -- if (summary_line) -- { -- /* %summary is special */ -- item_list = g_list_append(NULL, xstrdup(skip_whitespace(value))); -- } -- else -- { -- *dst = '\0'; /* terminate value (or key) */ -- if (value) -- item_list = split_string_on_char(value, ','); -- } -- -- section_t *sec = xzalloc(sizeof(*sec)); -- sec->name = xstrdup(line); -- sec->items = item_list; -- sections = g_list_prepend(sections, sec); -- -- free_line: -- free(line); -- } -- -- if (fp != stdin) -- fclose(fp); -- -- return g_list_reverse(sections); --} -- -- --/* Summary generation */ -- --#define MAX_OPT_DEPTH 10 --static --char *format_percented_string(const char *str, problem_data_t *pd) --{ -- size_t old_pos[MAX_OPT_DEPTH] = { 0 }; -- int okay[MAX_OPT_DEPTH] = { 1 }; -- int opt_depth = 1; -- struct strbuf *result = strbuf_new(); -- -- while (*str) { -- switch (*str) { -- default: -- strbuf_append_char(result, *str); -- str++; -- break; -- case '\\': -- if (str[1]) -- str++; -- strbuf_append_char(result, *str); -- str++; -- break; -- case '[': -- if (str[1] == '[' && opt_depth < MAX_OPT_DEPTH) -- { -- old_pos[opt_depth] = result->len; -- okay[opt_depth] = 1; -- opt_depth++; -- str += 2; -- } else { -- strbuf_append_char(result, *str); -- str++; -- } -- break; -- case ']': -- if (str[1] == ']' && opt_depth > 1) -- { -- opt_depth--; -- if (!okay[opt_depth]) -- { -- result->len = old_pos[opt_depth]; -- result->buf[result->len] = '\0'; -- } -- str += 2; -- } else { -- strbuf_append_char(result, *str); -- str++; -- } -- break; -- case '%': ; -- char *nextpercent = strchr(++str, '%'); -- if (!nextpercent) -- { -- error_msg_and_die("Unterminated %%element%%: '%s'", str - 1); -- } -- -- *nextpercent = '\0'; -- const problem_item *item = problem_data_get_item_or_NULL(pd, str); -- *nextpercent = '%'; -- -- if (item && (item->flags & CD_FLAG_TXT)) -- strbuf_append_str(result, item->content); -- else -- okay[opt_depth - 1] = 0; -- str = nextpercent + 1; -- break; -- } -- } -- -- if (opt_depth > 1) -- { -- error_msg_and_die("Unbalanced [[ ]] bracket"); -- } -- -- if (!okay[0]) -- { -- error_msg("Undefined variable outside of [[ ]] bracket"); -- } -- -- return strbuf_free_nobuf(result); --} -- --static --char *create_summary_string(problem_data_t *pd, GList *comment_fmt_spec) --{ -- GList *l = comment_fmt_spec; -- while (l) -- { -- section_t *sec = l->data; -- l = l->next; -- -- /* Find %summary" */ -- if (strcmp(sec->name, "%summary") != 0) -- continue; -- -- GList *item = sec->items; -- if (!item) -- /* not supposed to happen, there will be at least "" */ -- error_msg_and_die("BUG in %%summary parser"); -- -- const char *str = item->data; -- return format_percented_string(str, pd); -- } -- -- return format_percented_string("%reason%", pd); --} -- -- --/* BZ comment generation */ -- --static --int append_text(struct strbuf *result, const char *item_name, const char *content, bool print_item_name) --{ -- char *eol = strchrnul(content, '\n'); -- if (eol[0] == '\0' || eol[1] == '\0') -- { -- /* one-liner */ -- int pad = 16 - (strlen(item_name) + 2); -- if (pad < 0) -- pad = 0; -- if (print_item_name) -- strbuf_append_strf(result, -- eol[0] == '\0' ? "%s: %*s%s\n" : "%s: %*s%s", -- item_name, pad, "", content -- ); -- else -- strbuf_append_strf(result, -- eol[0] == '\0' ? "%s\n" : "%s", -- content -- ); -- } -- else -- { -- /* multi-line item */ -- if (print_item_name) -- strbuf_append_strf(result, "%s:\n", item_name); -- for (;;) -- { -- eol = strchrnul(content, '\n'); -- strbuf_append_strf(result, -- /* For %bare_multiline_item, we don't want to print colons */ -- (print_item_name ? ":%.*s\n" : "%.*s\n"), -- (int)(eol - content), content -- ); -- if (eol[0] == '\0' || eol[1] == '\0') -- break; -- content = eol + 1; -- } -- } -- return 1; --} -- --static --int append_short_backtrace(struct strbuf *result, problem_data_t *problem_data, size_t max_text_size, bool print_item_name) --{ -- const problem_item *item = problem_data_get_item_or_NULL(problem_data, -- FILENAME_BACKTRACE); -- if (!item) -- return 0; /* "I did not print anything" */ -- if (!(item->flags & CD_FLAG_TXT)) -- return 0; /* "I did not print anything" */ -- -- char *truncated = NULL; -- -- if (strlen(item->content) >= max_text_size) -- { -- char *error_msg = NULL; -- const char *analyzer = problem_data_get_content_or_NULL(problem_data, FILENAME_ANALYZER); -- if (!analyzer) -- return 0; -- -- /* For CCpp crashes, use the GDB-produced backtrace which should be -- * available by now. sr_abrt_type_from_analyzer returns SR_REPORT_CORE -- * by default for CCpp crashes. -- */ -- enum sr_report_type report_type = sr_abrt_type_from_analyzer(analyzer); -- if (strcmp(analyzer, "CCpp") == 0) -- report_type = SR_REPORT_GDB; -- -- struct sr_stacktrace *backtrace = sr_stacktrace_parse(report_type, -- item->content, &error_msg); -- -- if (!backtrace) -- { -- log(_("Can't parse backtrace: %s"), error_msg); -- free(error_msg); -- return 0; -- } -- -- /* Get optimized thread stack trace for 10 top most frames */ -- truncated = sr_stacktrace_to_short_text(backtrace, 10); -- sr_stacktrace_free(backtrace); -- -- if (!truncated) -- { -- log(_("Can't generate stacktrace description (no crash thread?)")); -- return 0; -- } -- } -- -- append_text(result, -- /*item_name:*/ truncated ? "truncated_backtrace" : FILENAME_BACKTRACE, -- /*content:*/ truncated ? truncated : item->content, -- print_item_name -- ); -- free(truncated); -- return 1; --} -- --static --int append_item(struct strbuf *result, const char *item_name, problem_data_t *pd, GList *comment_fmt_spec) --{ -- bool print_item_name = (strncmp(item_name, "%bare_", strlen("%bare_")) != 0); -- if (!print_item_name) -- item_name += strlen("%bare_"); -- -- if (item_name[0] != '%') -- { -- struct problem_item *item = problem_data_get_item_or_NULL(pd, item_name); -- if (!item) -- return 0; /* "I did not print anything" */ -- if (!(item->flags & CD_FLAG_TXT)) -- return 0; /* "I did not print anything" */ -- -- char *formatted = problem_item_format(item); -- char *content = formatted ? formatted : item->content; -- append_text(result, item_name, content, print_item_name); -- free(formatted); -- return 1; /* "I printed something" */ -- } -- -- /* Special item name */ -- -- /* Compat with previously-existed ad-hockery: %short_backtrace */ -- if (strcmp(item_name, "%short_backtrace") == 0) -- return append_short_backtrace(result, pd, CD_TEXT_ATT_SIZE_BZ, print_item_name); -- -- /* Compat with previously-existed ad-hockery: %reporter */ -- if (strcmp(item_name, "%reporter") == 0) -- return append_text(result, "reporter", PACKAGE"-"VERSION, print_item_name); -- -- /* %oneline,%multiline,%text */ -- bool oneline = (strcmp(item_name+1, "oneline" ) == 0); -- bool multiline = (strcmp(item_name+1, "multiline") == 0); -- bool text = (strcmp(item_name+1, "text" ) == 0); -- if (!oneline && !multiline && !text) -- { -- log("Unknown or unsupported element specifier '%s'", item_name); -- return 0; /* "I did not print anything" */ -- } -- -- int printed = 0; -- -- /* Iterate over _sorted_ items */ -- GList *sorted_names = g_hash_table_get_keys(pd); -- sorted_names = g_list_sort(sorted_names, (GCompareFunc)strcmp); -- -- /* %text => do as if %oneline, then repeat as if %multiline */ -- if (text) -- oneline = 1; -- -- again: ; -- GList *l = sorted_names; -- while (l) -- { -- const char *name = l->data; -- l = l->next; -- struct problem_item *item = g_hash_table_lookup(pd, name); -- if (!item) -- continue; /* paranoia, won't happen */ -- -- if (!(item->flags & CD_FLAG_TXT)) -- continue; -- -- if (is_explicit_or_forbidden(name, comment_fmt_spec)) -- continue; -- -- char *formatted = problem_item_format(item); -- char *content = formatted ? formatted : item->content; -- char *eol = strchrnul(content, '\n'); -- bool is_oneline = (eol[0] == '\0' || eol[1] == '\0'); -- if (oneline == is_oneline) -- printed |= append_text(result, name, content, print_item_name); -- free(formatted); -- } -- if (text && oneline) -- { -- /* %text, and we just did %oneline. Repeat as if %multiline */ -- oneline = 0; -- /*multiline = 1; - not checked in fact, so why bother setting? */ -- goto again; -- } -- -- g_list_free(sorted_names); /* names themselves are not freed */ -- -- return printed; --} -- --static --void generate_bz_comment(struct strbuf *result, problem_data_t *pd, GList *comment_fmt_spec) --{ -- bool last_line_is_empty = true; -- GList *l = comment_fmt_spec; -- while (l) -- { -- section_t *sec = l->data; -- l = l->next; -- -- /* Skip special sections such as "%attach" */ -- if (sec->name[0] == '%') -- continue; -- -- if (sec->items) -- { -- /* "Text: item[,item]..." */ -- struct strbuf *output = strbuf_new(); -- GList *item = sec->items; -- while (item) -- { -- const char *str = item->data; -- item = item->next; -- if (str[0] == '-') /* "-name", ignore it */ -- continue; -- append_item(output, str, pd, comment_fmt_spec); -- } -- -- if (output->len != 0) -- { -- strbuf_append_strf(result, -- sec->name[0] ? "%s:\n%s" : "%s%s", -- sec->name, -- output->buf -- ); -- last_line_is_empty = false; -- } -- strbuf_free(output); -- } -- else -- { -- /* Just "Text" (can be "") */ -- -- /* Filter out consecutive empty lines */ -- if (sec->name[0] != '\0' || !last_line_is_empty) -- strbuf_append_strf(result, "%s\n", sec->name); -- last_line_is_empty = (sec->name[0] == '\0'); -- } -- } -- -- /* Nuke any trailing empty lines */ -- while (result->len >= 1 -- && result->buf[result->len-1] == '\n' -- && (result->len == 1 || result->buf[result->len-2] == '\n') -- ) { -- result->buf[--result->len] = '\0'; -- } --} -- -- - /* BZ attachments */ - - static -@@ -573,104 +69,6 @@ int attach_file_item(struct abrt_xmlrpc *ax, const char *bug_id, - return (r == 0); - } - --static --int attach_item(struct abrt_xmlrpc *ax, const char *bug_id, -- const char *item_name, problem_data_t *pd, GList *comment_fmt_spec) --{ -- if (item_name[0] != '%') -- { -- struct problem_item *item = problem_data_get_item_or_NULL(pd, item_name); -- if (!item) -- return 0; -- if (item->flags & CD_FLAG_TXT) -- return attach_text_item(ax, bug_id, item_name, item); -- if (item->flags & CD_FLAG_BIN) -- return attach_file_item(ax, bug_id, item_name, item); -- return 0; -- } -- -- /* Special item name */ -- -- /* %oneline,%multiline,%text,%binary */ -- bool oneline = (strcmp(item_name+1, "oneline" ) == 0); -- bool multiline = (strcmp(item_name+1, "multiline") == 0); -- bool text = (strcmp(item_name+1, "text" ) == 0); -- bool binary = (strcmp(item_name+1, "binary" ) == 0); -- if (!oneline && !multiline && !text && !binary) -- { -- log("Unknown or unsupported element specifier '%s'", item_name); -- return 0; -- } -- -- log_debug("Special item_name '%s', iterating for attach...", item_name); -- int done = 0; -- -- /* Iterate over _sorted_ items */ -- GList *sorted_names = g_hash_table_get_keys(pd); -- sorted_names = g_list_sort(sorted_names, (GCompareFunc)strcmp); -- -- GList *l = sorted_names; -- while (l) -- { -- const char *name = l->data; -- l = l->next; -- struct problem_item *item = g_hash_table_lookup(pd, name); -- if (!item) -- continue; /* paranoia, won't happen */ -- -- if (is_explicit_or_forbidden(name, comment_fmt_spec)) -- continue; -- -- if ((item->flags & CD_FLAG_TXT) && !binary) -- { -- char *content = item->content; -- char *eol = strchrnul(content, '\n'); -- bool is_oneline = (eol[0] == '\0' || eol[1] == '\0'); -- if (text || oneline == is_oneline) -- done |= attach_text_item(ax, bug_id, name, item); -- } -- if ((item->flags & CD_FLAG_BIN) && binary) -- done |= attach_file_item(ax, bug_id, name, item); -- } -- -- g_list_free(sorted_names); /* names themselves are not freed */ -- -- -- log_debug("...Done iterating over '%s' for attach", item_name); -- -- return done; --} -- --static --int attach_files(struct abrt_xmlrpc *ax, const char *bug_id, -- problem_data_t *pd, GList *comment_fmt_spec) --{ -- int done = 0; -- GList *l = comment_fmt_spec; -- while (l) -- { -- section_t *sec = l->data; -- l = l->next; -- -- /* Find %attach" */ -- if (strcmp(sec->name, "%attach") != 0) -- continue; -- -- GList *item = sec->items; -- while (item) -- { -- const char *str = item->data; -- item = item->next; -- if (str[0] == '-') /* "-name", ignore it */ -- continue; -- done |= attach_item(ax, bug_id, str, pd, comment_fmt_spec); -- } -- } -- -- return done; --} -- -- - /* Main */ - - struct bugzilla_struct { -@@ -1102,18 +500,29 @@ int main(int argc, char **argv) - - if (opts & OPT_D) - { -- GList *comment_fmt_spec = load_bzrep_conf_file(fmt_file); -- struct strbuf *bzcomment_buf = strbuf_new(); -- generate_bz_comment(bzcomment_buf, problem_data, comment_fmt_spec); -- char *bzcomment = strbuf_free_nobuf(bzcomment_buf); -- char *summary = create_summary_string(problem_data, comment_fmt_spec); -+ problem_formatter_t *pf = problem_formatter_new(); -+ -+ if (problem_formatter_load_file(pf, fmt_file)) -+ error_msg_and_die("Invalid format file: %s", fmt_file); -+ -+ problem_report_t *pr = NULL; -+ if (problem_formatter_generate_report(pf, problem_data, &pr)) -+ error_msg_and_die("Failed to format bug report from problem data"); -+ - printf("summary: %s\n" - "\n" - "%s" -- , summary, bzcomment -+ "\n" -+ , problem_report_get_summary(pr) -+ , problem_report_get_description(pr) - ); -- free(bzcomment); -- free(summary); -+ -+ puts("attachments:"); -+ for (GList *a = problem_report_get_attachments(pr); a != NULL; a = g_list_next(a)) -+ printf(" %s\n", (const char *)a->data); -+ -+ problem_report_free(pr); -+ problem_formatter_free(pf); - exit(0); - } - -@@ -1196,22 +605,29 @@ int main(int argc, char **argv) - /* Create new bug */ - log(_("Creating a new bug")); - -- GList *comment_fmt_spec = load_bzrep_conf_file(fmt_file); -+ problem_formatter_t *pf = problem_formatter_new(); -+ -+ if (problem_formatter_load_file(pf, fmt_file)) -+ error_msg_and_die("Invalid format file: %s", fmt_file); -+ -+ problem_report_t *pr = NULL; -+ if (problem_formatter_generate_report(pf, problem_data, &pr)) -+ error_msg_and_die("Failed to format problem data"); - -- struct strbuf *bzcomment_buf = strbuf_new(); -- generate_bz_comment(bzcomment_buf, problem_data, comment_fmt_spec); - if (crossver_id >= 0) -- strbuf_append_strf(bzcomment_buf, "\nPotential duplicate: bug %u\n", crossver_id); -- char *bzcomment = strbuf_free_nobuf(bzcomment_buf); -- char *summary = create_summary_string(problem_data, comment_fmt_spec); -+ problem_report_buffer_printf( -+ problem_report_get_buffer(pr, PR_SEC_DESCRIPTION), -+ "\nPotential duplicate: bug %u\n", crossver_id); -+ -+ problem_formatter_free(pf); -+ - int new_id = rhbz_new_bug(client, - problem_data, rhbz.b_product, rhbz.b_product_version, -- summary, bzcomment, -+ problem_report_get_summary(pr), -+ problem_report_get_description(pr), - (rhbz.b_create_private | (opts & OPT_g)), // either we got Bugzilla_CreatePrivate from settings or -g was specified on cmdline - rhbz.b_private_groups - ); -- free(bzcomment); -- free(summary); - - if (new_id == -1) - { -@@ -1236,9 +652,17 @@ int main(int argc, char **argv) - char new_id_str[sizeof(int)*3 + 2]; - sprintf(new_id_str, "%i", new_id); - -- attach_files(client, new_id_str, problem_data, comment_fmt_spec); -- --//TODO: free_comment_fmt_spec(comment_fmt_spec); -+ for (GList *a = problem_report_get_attachments(pr); a != NULL; a = g_list_next(a)) -+ { -+ const char *item_name = (const char *)a->data; -+ struct problem_item *item = problem_data_get_item_or_NULL(problem_data, item_name); -+ if (!item) -+ continue; -+ else if (item->flags & CD_FLAG_TXT) -+ attach_text_item(client, new_id_str, item_name, item); -+ else if (item->flags & CD_FLAG_BIN) -+ attach_file_item(client, new_id_str, item_name, item); -+ } - - bz = new_bug_info(); - bz->bi_status = xstrdup("NEW"); -@@ -1302,18 +726,21 @@ int main(int argc, char **argv) - const char *comment = problem_data_get_content_or_NULL(problem_data, FILENAME_COMMENT); - if (comment && comment[0]) - { -- GList *comment_fmt_spec = load_bzrep_conf_file(fmt_file2); -- struct strbuf *bzcomment_buf = strbuf_new(); -- generate_bz_comment(bzcomment_buf, problem_data, comment_fmt_spec); -- char *bzcomment = strbuf_free_nobuf(bzcomment_buf); --//TODO: free_comment_fmt_spec(comment_fmt_spec); -+ problem_formatter_t *pf = problem_formatter_new(); -+ if (problem_formatter_load_file(pf, fmt_file2)) -+ error_msg_and_die("Invalid duplicate format file: '%s", fmt_file2); -+ -+ problem_report_t *pr; -+ if (problem_formatter_generate_report(pf, problem_data, &pr)) -+ error_msg_and_die("Failed to format duplicate comment from problem data"); -+ -+ const char *bzcomment = problem_report_get_description(pr); - - int dup_comment = is_comment_dup(bz->bi_comments, bzcomment); - if (!dup_comment) - { - log(_("Adding new comment to bug %d"), bz->bi_id); - rhbz_add_comment(client, bz->bi_id, bzcomment, 0); -- free(bzcomment); - - const char *bt = problem_data_get_content_or_NULL(problem_data, FILENAME_BACKTRACE); - unsigned rating = 0; -@@ -1331,10 +758,10 @@ int main(int argc, char **argv) - } - } - else -- { -- free(bzcomment); - log(_("Found the same comment in the bug history, not adding a new one")); -- } -+ -+ problem_report_free(pr); -+ problem_formatter_free(pf); - } - - log_out: --- -1.8.3.1 - diff --git a/SOURCES/0128-dump_dir-allow-hooks-to-create-dump-directory-withou.patch b/SOURCES/0128-dump_dir-allow-hooks-to-create-dump-directory-withou.patch new file mode 100644 index 0000000..f613ffc --- /dev/null +++ b/SOURCES/0128-dump_dir-allow-hooks-to-create-dump-directory-withou.patch @@ -0,0 +1,83 @@ +From e76a8655152129de09bd9521ade8158bb07cc8fe Mon Sep 17 00:00:00 2001 +From: Jakub Filak +Date: Wed, 15 Apr 2015 17:41:49 +0200 +Subject: [LIBREPORT PATCH] dump_dir: allow hooks to create dump directory + without parents + +With a centralized model of handling problems like ABRT, there is a need +to ensure that every dump directory is a descendant of some central +directory (database). This commit together with other security commits +makes code of the tools creating the dump directories in the central +directory more robust by ensuring that no tool accidentally creates the +central directory and all tools creates exactly one directory. + +Related: #1211835 + +Signed-off-by: Jakub Filak +--- + src/include/dump_dir.h | 4 +++- + src/lib/dump_dir.c | 12 +++++++++--- + 2 files changed, 12 insertions(+), 4 deletions(-) + +diff --git a/src/include/dump_dir.h b/src/include/dump_dir.h +index 71cf66f..8f672d3 100644 +--- a/src/include/dump_dir.h ++++ b/src/include/dump_dir.h +@@ -43,6 +43,8 @@ enum { + DD_OPEN_READONLY = (1 << 3), + DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE = (1 << 4), + DD_DONT_WAIT_FOR_LOCK = (1 << 5), ++ /* Create the new dump directory with parent directories (mkdir -p)*/ ++ DD_CREATE_PARENTS = (1 << 6), + }; + + struct dump_dir { +@@ -60,7 +62,7 @@ struct dump_dir { + void dd_close(struct dump_dir *dd); + + struct dump_dir *dd_opendir(const char *dir, int flags); +-struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode); ++struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int flags); + int dd_reset_ownership(struct dump_dir *dd); + /* Pass uid = (uid_t)-1L to disable chown'ing of newly created files + * (IOW: if you aren't running under root): +diff --git a/src/lib/dump_dir.c b/src/lib/dump_dir.c +index fabad0b..2a65100 100644 +--- a/src/lib/dump_dir.c ++++ b/src/lib/dump_dir.c +@@ -514,7 +514,7 @@ struct dump_dir *dd_opendir(const char *dir, int flags) + * this runs under 0:0 + * - clients: setroubleshootd, abrt python + */ +-struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode) ++struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int flags) + { + /* a little trick to copy read bits from file mode to exec bit of dir mode*/ + mode_t dir_mode = mode | ((mode & 0444) >> 2); +@@ -547,7 +547,13 @@ struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode) + * the user to replace any file in the directory, changing security-sensitive data + * (e.g. "uid", "analyzer", "executable") + */ +- if (g_mkdir_with_parents(dd->dd_dirname, dir_mode) != 0) ++ int r; ++ if ((flags & DD_CREATE_PARENTS)) ++ r = g_mkdir_with_parents(dd->dd_dirname, dir_mode); ++ else ++ r = mkdir(dd->dd_dirname, dir_mode); ++ ++ if (r != 0) + { + perror_msg("Can't create directory '%s'", dir); + dd_close(dd); +@@ -627,7 +633,7 @@ int dd_reset_ownership(struct dump_dir *dd) + */ + struct dump_dir *dd_create(const char *dir, uid_t uid, mode_t mode) + { +- struct dump_dir *dd = dd_create_skeleton(dir, uid, mode); ++ struct dump_dir *dd = dd_create_skeleton(dir, uid, mode, DD_CREATE_PARENTS); + if (dd == NULL) + return NULL; + +-- +1.8.3.1 + diff --git a/SOURCES/0129-lib-add-a-function-checking-file-names.patch b/SOURCES/0129-lib-add-a-function-checking-file-names.patch new file mode 100644 index 0000000..710579b --- /dev/null +++ b/SOURCES/0129-lib-add-a-function-checking-file-names.patch @@ -0,0 +1,155 @@ +From 54ecf8d017580b495d6501e53ca54e453a73a364 Mon Sep 17 00:00:00 2001 +From: Jakub Filak +Date: Thu, 23 Apr 2015 13:21:41 +0200 +Subject: [LIBREPORT PATCH] lib: add a function checking file names + +Move the code from ABRT and extend it a bit: +* allow only 64 characters +* allow '.' in names (vmcore_dmesg.txt) +* forbid '/' +* forbid "." +* forbid ".." + +Related: #1214451 + +Signed-off-by: Jakub Filak +--- + src/include/internal_libreport.h | 6 +++++ + src/lib/concat_path_file.c | 25 ++++++++++++++++++++ + tests/Makefile.am | 3 ++- + tests/dump_dir.at | 49 ++++++++++++++++++++++++++++++++++++++++ + tests/testsuite.at | 1 + + 5 files changed, 83 insertions(+), 1 deletion(-) + create mode 100644 tests/dump_dir.at + +diff --git a/src/include/internal_libreport.h b/src/include/internal_libreport.h +index 4c5c72a..8d84fd4 100644 +--- a/src/include/internal_libreport.h ++++ b/src/include/internal_libreport.h +@@ -132,6 +132,12 @@ char *concat_path_file(const char *path, const char *filename); + #define concat_path_basename libreport_concat_path_basename + char *concat_path_basename(const char *path, const char *filename); + ++/* Allows all printable characters except '/', ++ * the string must not exceed 64 characters of length ++ * and must not equal neither "." nor ".." (these strings may appear in the string) */ ++#define str_is_correct_filename libreport_str_is_correct_filename ++bool str_is_correct_filename(const char *str); ++ + /* A-la fgets, but malloced and of unlimited size */ + #define xmalloc_fgets libreport_xmalloc_fgets + char *xmalloc_fgets(FILE *file); +diff --git a/src/lib/concat_path_file.c b/src/lib/concat_path_file.c +index 39ae07a..24e4cbd 100644 +--- a/src/lib/concat_path_file.c ++++ b/src/lib/concat_path_file.c +@@ -57,3 +57,28 @@ char *concat_path_basename(const char *path, const char *filename) + free(abspath); + return name; + } ++ ++bool str_is_correct_filename(const char *str) ++{ ++#define NOT_PRINTABLE(c) (c < ' ' || c == 0x7f) ++ ++ if (NOT_PRINTABLE(*str) || *str == '/' || *str == '\0') ++ return false; ++ ++str; ++ ++ if (NOT_PRINTABLE(*str) || *str =='/' || (*str == '\0' && *(str-1) == '.')) ++ return false; ++ ++str; ++ ++ if (NOT_PRINTABLE(*str) || *str =='/' || (*str == '\0' && *(str-1) == '.' && *(str-2) == '.')) ++ return false; ++ ++str; ++ ++ for (unsigned i = 0; *str != '\0' && i < 61; ++str, ++i) ++ if (NOT_PRINTABLE(*str) || *str == '/') ++ return false; ++ ++ return *str == '\0'; ++ ++#undef NOT_PRINTABLE ++} +diff --git a/tests/Makefile.am b/tests/Makefile.am +index a680f05..eaf1ac2 100644 +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -42,7 +42,8 @@ TESTSUITE_AT = \ + report_python.at \ + xfuncs.at \ + string_list.at \ +- ureport.at ++ ureport.at \ ++ dump_dir.at + + EXTRA_DIST += $(TESTSUITE_AT) + TESTSUITE = $(srcdir)/testsuite +diff --git a/tests/dump_dir.at b/tests/dump_dir.at +new file mode 100644 +index 0000000..a579243 +--- /dev/null ++++ b/tests/dump_dir.at +@@ -0,0 +1,49 @@ ++# -*- Autotest -*- ++ ++AT_BANNER([dump directories]) ++ ++## ----------------------- ## ++## str_is_correct_filename ## ++## ----------------------- ## ++ ++AT_TESTFUN([str_is_correct_filename], ++[[ ++#include "internal_libreport.h" ++#include ++# ++int main(void) ++{ ++ g_verbose = 3; ++ ++ assert(str_is_correct_filename("") == false); ++ assert(str_is_correct_filename("/") == false); ++ assert(str_is_correct_filename("//") == false); ++ assert(str_is_correct_filename(".") == false); ++ assert(str_is_correct_filename(".") == false); ++ assert(str_is_correct_filename("..") == false); ++ assert(str_is_correct_filename("..") == false); ++ assert(str_is_correct_filename("/.") == false); ++ assert(str_is_correct_filename("//.") == false); ++ assert(str_is_correct_filename("./") == false); ++ assert(str_is_correct_filename(".//") == false); ++ assert(str_is_correct_filename("/./") == false); ++ assert(str_is_correct_filename("/..") == false); ++ assert(str_is_correct_filename("//..") == false); ++ assert(str_is_correct_filename("../") == false); ++ assert(str_is_correct_filename("..//") == false); ++ assert(str_is_correct_filename("/../") == false); ++ assert(str_is_correct_filename("/.././") == false); ++ ++ assert(str_is_correct_filename("looks-good-but-evil/") == false); ++ assert(str_is_correct_filename("looks-good-but-evil/../../") == false); ++ ++ assert(str_is_correct_filename(".meta-data") == true); ++ assert(str_is_correct_filename("..meta-meta-data") == true); ++ assert(str_is_correct_filename("meta-..-data") == true); ++ ++ assert(str_is_correct_filename("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+-") == true); ++ assert(str_is_correct_filename("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890+-=") == false); ++ ++ return 0; ++} ++]]) +diff --git a/tests/testsuite.at b/tests/testsuite.at +index abad32b..41107e7 100644 +--- a/tests/testsuite.at ++++ b/tests/testsuite.at +@@ -17,3 +17,4 @@ m4_include([xml_definition.at]) + m4_include([report_python.at]) + m4_include([string_list.at]) + m4_include([ureport.at]) ++m4_include([dump_dir.at]) +-- +1.8.3.1 + diff --git a/SOURCES/0130-dd-harden-functions-against-directory-traversal-issu.patch b/SOURCES/0130-dd-harden-functions-against-directory-traversal-issu.patch new file mode 100644 index 0000000..d223371 --- /dev/null +++ b/SOURCES/0130-dd-harden-functions-against-directory-traversal-issu.patch @@ -0,0 +1,133 @@ +From 239c4f7d1f47265526b39ad70106767d00805277 Mon Sep 17 00:00:00 2001 +From: Jakub Filak +Date: Thu, 23 Apr 2015 13:30:15 +0200 +Subject: [LIBREPORT PATCH] dd: harden functions against directory traversal + issues + +Test correctness of all accessed dump dir files in all dd* functions. +Before this commit, the callers were allowed to pass strings like +"../../etc/shadow" in the filename argument of all dd* functions. + +Related: #1214457 + +Signed-off-by: Jakub Filak +--- + src/lib/create_dump_dir.c | 19 ++++++++++++------- + src/lib/dump_dir.c | 22 ++++++++++++++++++++++ + 2 files changed, 34 insertions(+), 7 deletions(-) + +diff --git a/src/lib/create_dump_dir.c b/src/lib/create_dump_dir.c +index 4f67523..989a50c 100644 +--- a/src/lib/create_dump_dir.c ++++ b/src/lib/create_dump_dir.c +@@ -42,6 +42,12 @@ struct dump_dir *create_dump_dir_from_problem_data(problem_data_t *problem_data, + return NULL; + } + ++ if (!str_is_correct_filename(type)) ++ { ++ error_msg(_("'%s' is not correct file name"), FILENAME_ANALYZER); ++ return NULL; ++ } ++ + uid_t uid = (uid_t)-1L; + char *uid_str = problem_data_get_content_or_NULL(problem_data, FILENAME_UID); + +@@ -105,6 +111,12 @@ struct dump_dir *create_dump_dir_from_problem_data(problem_data_t *problem_data, + g_hash_table_iter_init(&iter, problem_data); + while (g_hash_table_iter_next(&iter, (void**)&name, (void**)&value)) + { ++ if (!str_is_correct_filename(name)) ++ { ++ error_msg("Problem data field name contains disallowed chars: '%s'", name); ++ continue; ++ } ++ + if (value->flags & CD_FLAG_BIN) + { + char *dest = concat_path_file(dd->dd_dirname, name); +@@ -119,13 +131,6 @@ struct dump_dir *create_dump_dir_from_problem_data(problem_data_t *problem_data, + continue; + } + +- /* only files should contain '/' and those are handled earlier */ +- if (name[0] == '.' || strchr(name, '/')) +- { +- error_msg("Problem data field name contains disallowed chars: '%s'", name); +- continue; +- } +- + dd_save_text(dd, name, value->content); + } + +diff --git a/src/lib/dump_dir.c b/src/lib/dump_dir.c +index 2a65100..0048faf 100644 +--- a/src/lib/dump_dir.c ++++ b/src/lib/dump_dir.c +@@ -345,6 +345,9 @@ static inline struct dump_dir *dd_init(void) + + int dd_exist(const struct dump_dir *dd, const char *path) + { ++ if (!str_is_correct_filename(path)) ++ error_msg_and_die("Cannot test existence. '%s' is not a valid file name", path); ++ + char *full_path = concat_path_file(dd->dd_dirname, path); + int ret = exist_file_dir(full_path); + free(full_path); +@@ -1044,6 +1047,13 @@ char* dd_load_text_ext(const struct dump_dir *dd, const char *name, unsigned fla + // if (!dd->locked) + // error_msg_and_die("dump_dir is not opened"); /* bug */ + ++ if (!str_is_correct_filename(name)) ++ { ++ error_msg("Cannot load text. '%s' is not a valid file name", name); ++ if (!(flags & DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE)) ++ xfunc_die(); ++ } ++ + /* Compat with old abrt dumps. Remove in abrt-2.1 */ + if (strcmp(name, "release") == 0) + name = FILENAME_OS_RELEASE; +@@ -1065,6 +1075,9 @@ void dd_save_text(struct dump_dir *dd, const char *name, const char *data) + if (!dd->locked) + error_msg_and_die("dump_dir is not opened"); /* bug */ + ++ if (!str_is_correct_filename(name)) ++ error_msg_and_die("Cannot save text. '%s' is not a valid file name", name); ++ + char *full_path = concat_path_file(dd->dd_dirname, name); + save_binary_file(full_path, data, strlen(data), dd->dd_uid, dd->dd_gid, dd->mode); + free(full_path); +@@ -1075,6 +1088,9 @@ void dd_save_binary(struct dump_dir* dd, const char* name, const char* data, uns + if (!dd->locked) + error_msg_and_die("dump_dir is not opened"); /* bug */ + ++ if (!str_is_correct_filename(name)) ++ error_msg_and_die("Cannot save binary. '%s' is not a valid file name", name); ++ + char *full_path = concat_path_file(dd->dd_dirname, name); + save_binary_file(full_path, data, size, dd->dd_uid, dd->dd_gid, dd->mode); + free(full_path); +@@ -1082,6 +1098,9 @@ void dd_save_binary(struct dump_dir* dd, const char* name, const char* data, uns + + long dd_get_item_size(struct dump_dir *dd, const char *name) + { ++ if (!str_is_correct_filename(name)) ++ error_msg_and_die("Cannot get item size. '%s' is not a valid file name", name); ++ + long size = -1; + char *iname = concat_path_file(dd->dd_dirname, name); + struct stat statbuf; +@@ -1106,6 +1125,9 @@ int dd_delete_item(struct dump_dir *dd, const char *name) + if (!dd->locked) + error_msg_and_die("dump_dir is not opened"); /* bug */ + ++ if (!str_is_correct_filename(name)) ++ error_msg_and_die("Cannot delete item. '%s' is not a valid file name", name); ++ + char *path = concat_path_file(dd->dd_dirname, name); + int res = unlink(path); + +-- +1.8.3.1 + diff --git a/SOURCES/0130-lib-created-a-new-lib-file-for-reporters.patch b/SOURCES/0130-lib-created-a-new-lib-file-for-reporters.patch deleted file mode 100644 index 7a20cbc..0000000 --- a/SOURCES/0130-lib-created-a-new-lib-file-for-reporters.patch +++ /dev/null @@ -1,405 +0,0 @@ -From 5a6e03181d06d9350f181d21ab103bb0c07ffb86 Mon Sep 17 00:00:00 2001 -From: Matej Habrnal -Date: Tue, 13 Jan 2015 19:23:08 -0500 -Subject: [PATCH 130/136] lib: created a new lib file for reporters - -Moved some functions from rhbz.c to src/lib/reporters.c and src/lib/strbuf.c. - -Related to #272 - -Signed-off-by: Matej Habrnal ---- - po/POTFILES.in | 1 + - src/include/Makefile.am | 3 +- - src/include/internal_libreport.h | 5 +++ - src/include/reporters.h | 36 ++++++++++++++++++ - src/lib/Makefile.am | 3 +- - src/lib/reporters.c | 80 ++++++++++++++++++++++++++++++++++++++++ - src/lib/strbuf.c | 60 ++++++++++++++++++++++++++++++ - src/plugins/rhbz.c | 78 +-------------------------------------- - src/plugins/rhbz.h | 2 - - 9 files changed, 188 insertions(+), 80 deletions(-) - create mode 100644 src/include/reporters.h - create mode 100644 src/lib/reporters.c - -diff --git a/po/POTFILES.in b/po/POTFILES.in -index c597b11..5588540 100644 ---- a/po/POTFILES.in -+++ b/po/POTFILES.in -@@ -24,6 +24,7 @@ src/lib/make_descr.c - src/lib/parse_options.c - src/lib/problem_data.c - src/lib/problem_report.c -+src/lib/reporters.c - src/lib/run_event.c - src/plugins/abrt_rh_support.c - src/plugins/report_Bugzilla.xml.in -diff --git a/src/include/Makefile.am b/src/include/Makefile.am -index 47ba399..a13e04d 100644 ---- a/src/include/Makefile.am -+++ b/src/include/Makefile.am -@@ -15,7 +15,8 @@ libreport_include_HEADERS = \ - file_obj.h \ - internal_libreport.h \ - internal_abrt_dbus.h \ -- xml_parser.h -+ xml_parser.h \ -+ reporters.h - - if BUILD_UREPORT - libreport_include_HEADERS += ureport.h -diff --git a/src/include/internal_libreport.h b/src/include/internal_libreport.h -index 967324b..b689b74 100644 ---- a/src/include/internal_libreport.h -+++ b/src/include/internal_libreport.h -@@ -98,6 +98,7 @@ int vdprintf(int d, const char *format, va_list ap); - #include "workflow.h" - #include "file_obj.h" - #include "libreport_types.h" -+#include "reporters.h" - - #ifdef __cplusplus - extern "C" { -@@ -107,6 +108,10 @@ extern "C" { - int prefixcmp(const char *str, const char *prefix); - #define suffixcmp libreport_suffixcmp - int suffixcmp(const char *str, const char *suffix); -+#define trim_all_whitespace libreport_trim_all_whitespace -+char *trim_all_whitespace(const char *str); -+#define shorten_string_to_length libreport_shorten_string_to_length -+char *shorten_string_to_length(const char *str, unsigned length); - #define strtrim libreport_strtrim - char *strtrim(char *str); - #define strtrimch libreport_strtrimch -diff --git a/src/include/reporters.h b/src/include/reporters.h -new file mode 100644 -index 0000000..d415b7f ---- /dev/null -+++ b/src/include/reporters.h -@@ -0,0 +1,36 @@ -+/* -+ Copyright (C) 2014 ABRT team -+ Copyright (C) 2014 RedHat Inc -+ -+ This program is free software; you can redistribute it and/or modify -+ it under the terms of the GNU General Public License as published by -+ the Free Software Foundation; either version 2 of the License, or -+ (at your option) any later version. -+ -+ This program is distributed in the hope that it will be useful, -+ but WITHOUT ANY WARRANTY; without even the implied warranty of -+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ GNU General Public License for more details. -+ -+ You should have received a copy of the GNU General Public License along -+ with this program; if not, write to the Free Software Foundation, Inc., -+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -+*/ -+ -+#ifndef REPORTERS_H -+#define REPORTERS_H -+ -+#ifdef __cplusplus -+extern "C" { -+#endif -+ -+#define is_comment_dup libreport_is_comment_dup -+int is_comment_dup(GList *comments, const char *comment); -+#define comments_find_best_bt_rating libreport_comments_find_best_bt_rating -+unsigned comments_find_best_bt_rating(GList *comments); -+ -+#ifdef __cplusplus -+} -+#endif -+ -+#endif -diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am -index a0001ef..3ec463f 100644 ---- a/src/lib/Makefile.am -+++ b/src/lib/Makefile.am -@@ -56,7 +56,8 @@ libreport_la_SOURCES = \ - workflow_xml_parser.c \ - config_item_info.c \ - xml_parser.c \ -- libreport_init.c -+ libreport_init.c \ -+ reporters.c - - libreport_la_CPPFLAGS = \ - -I$(srcdir)/../include \ -diff --git a/src/lib/reporters.c b/src/lib/reporters.c -new file mode 100644 -index 0000000..e3305ca ---- /dev/null -+++ b/src/lib/reporters.c -@@ -0,0 +1,80 @@ -+/* -+ String buffer implementation -+ -+ Copyright (C) 2015 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 "internal_libreport.h" -+ -+int -+is_comment_dup(GList *comments, const char *comment) -+{ -+ char * const trim_comment = trim_all_whitespace(comment); -+ bool same_comments = false; -+ -+ for (GList *l = comments; l && !same_comments; l = l->next) -+ { -+ const char * const comment_body = (const char *) l->data; -+ char * const trim_comment_body = trim_all_whitespace(comment_body); -+ same_comments = (strcmp(trim_comment_body, trim_comment) == 0); -+ free(trim_comment_body); -+ } -+ -+ free(trim_comment); -+ return same_comments; -+} -+ -+unsigned -+comments_find_best_bt_rating(GList *comments) -+{ -+ if (comments == NULL) -+ return 0; -+ -+ unsigned best_rating = 0; -+ for (GList *l = comments; l; l = l->next) -+ { -+ char *comment_body = (char *) l->data; -+ -+ char *start_rating_line = strstr(comment_body, FILENAME_RATING": "); -+ if (!start_rating_line) -+ { -+ log_debug(_("Note does not contain rating")); -+ continue; -+ } -+ -+ start_rating_line += strlen(FILENAME_RATING": "); -+ -+ errno = 0; -+ char *e; -+ long rating = strtoul(start_rating_line, &e, 10); -+ /* -+ * Note: we intentionally check for '\n'. Any other terminator -+ * (even '\0') is not ok in this case. -+ */ -+ if (errno || e == start_rating_line || (*e != '\n' && *e != '\r') || (unsigned long)rating > UINT_MAX) -+ { -+ /* error / no digits / illegal trailing chars */ -+ continue; -+ } -+ -+ if (rating > best_rating) -+ best_rating = rating; -+ } -+ -+ return best_rating; -+} -+ -diff --git a/src/lib/strbuf.c b/src/lib/strbuf.c -index ef8bda8..f0cd1b8 100644 ---- a/src/lib/strbuf.c -+++ b/src/lib/strbuf.c -@@ -37,6 +37,66 @@ int suffixcmp(const char *str, const char *suffix) - return strcmp(str + len_minus_suflen, suffix); - } - -+char *trim_all_whitespace(const char *str) -+{ -+ char *trim = xzalloc(sizeof(char) * strlen(str) + 1); -+ int i = 0; -+ while (*str) -+ { -+ if (!isspace(*str)) -+ trim[i++] = *str; -+ str++; -+ } -+ -+ return trim; -+} -+ -+/* If str is longer than max allowed length then -+ * try to find first ' ' from the end of acceptable long str string -+ * -+ * If ' ' is found replace string after that by "..." -+ * -+ * If ' ' is NOT found in maximal allowed range, cut str string on -+ * lenght (MAX_SUMMARY_LENGTH - strlen("...")) and append "..." -+ * -+ * If MAX_LENGTH is 15 and max allowed cut is 5: -+ * -+ * 0123456789ABCDEF -> 0123456789AB... -+ * 0123456789 BCDEF -> 0123456789 ... -+ * 012345 789ABCDEF -> 012345 789AB... -+ */ -+char * -+shorten_string_to_length(const char *str, unsigned length) -+{ -+ char *dup_str = xstrdup(str); -+ if (strlen(str) > length) -+ { -+ char *max_end = dup_str + (length - strlen("...")); -+ -+ /* maximal number of characters to cut due to attempt cut dup_str -+ * string after last ' ' -+ */ -+ int max_cut = 16; -+ -+ /* start looking for ' ' one char before the last possible character */ -+ char *buf = max_end - 1; -+ while (buf[0] != ' ' && max_cut--) -+ --buf; -+ -+ if (buf[0] != ' ') -+ buf = max_end; -+ else -+ ++buf; -+ -+ buf[0] = '.'; -+ buf[1] = '.'; -+ buf[2] = '.'; -+ buf[3] = '\0'; -+ } -+ -+ return dup_str; -+} -+ - /* - * Trims whitespace characters both from left and right side of a string. - * Modifies the string in-place. Returns the trimmed string. -diff --git a/src/plugins/rhbz.c b/src/plugins/rhbz.c -index bad9ed4..a376c13 100644 ---- a/src/plugins/rhbz.c -+++ b/src/plugins/rhbz.c -@@ -133,41 +133,6 @@ static GList *rhbz_comments(struct abrt_xmlrpc *ax, int bug_id) - return g_list_reverse(comments); - } - --static char *trim_all_whitespace(const char *str) --{ -- func_entry(); -- -- char *trim = xzalloc(sizeof(char) * strlen(str) + 1); -- int i = 0; -- while (*str) -- { -- if (!isspace(*str)) -- trim[i++] = *str; -- str++; -- } -- -- return trim; --} -- --int is_comment_dup(GList *comments, const char *comment) --{ -- func_entry(); -- -- char * const trim_comment = trim_all_whitespace(comment); -- bool same_comments = false; -- -- for (GList *l = comments; l && !same_comments; l = l->next) -- { -- const char * const comment_body = (const char *) l->data; -- char * const trim_comment_body = trim_all_whitespace(comment_body); -- same_comments = (strcmp(trim_comment_body, trim_comment) == 0); -- free(trim_comment_body); -- } -- -- free(trim_comment); -- return same_comments; --} -- - static unsigned find_best_bt_rating_in_comments(GList *comments) - { - func_entry(); -@@ -553,46 +518,7 @@ int rhbz_new_bug(struct abrt_xmlrpc *ax, - if (!duphash) duphash = problem_data_get_content_or_NULL(problem_data, - "global_uuid"); - -- /* If summary is longer than max allowed summary length then -- * try to find first ' ' from the end of acceptable long summary string -- * -- * If ' ' is found replace string after that by "..." -- * -- * If ' ' is NOT found in maximal allowed range, cut summary string on -- * lenght (MAX_SUMMARY_LENGTH - strlen("...")) and append "..." -- * -- * If MAX_SUMMARY_LENGTH is 15 and max allowed cut is 5: -- * -- * 0123456789ABCDEF -> 0123456789AB... -- * 0123456789 BCDEF -> 0123456789 ... -- * 012345 789ABCDEF -> 012345 789AB... -- */ -- char *summary = NULL; -- if (strlen(bzsummary) > MAX_SUMMARY_LENGTH) -- { -- summary = xstrdup(bzsummary); -- char *max_end = summary + (MAX_SUMMARY_LENGTH - strlen("...")); -- -- /* maximal number of characters to cut due to attempt cut summary -- * string after last ' ' -- */ -- int max_cut = 16; -- -- /* start looking for ' ' one char before the last possible character */ -- char *buf = max_end - 1; -- while (buf[0] != ' ' && max_cut--) -- --buf; -- -- if (buf[0] != ' ') -- buf = max_end; -- else -- ++buf; -- -- buf[0] = '.'; -- buf[1] = '.'; -- buf[2] = '.'; -- buf[3] = '\0'; -- } -+ char *summary = shorten_string_to_length(bzsummary, MAX_SUMMARY_LENGTH); - - char *status_whiteboard = xasprintf("abrt_hash:%s", duphash); - -@@ -604,7 +530,7 @@ int rhbz_new_bug(struct abrt_xmlrpc *ax, - abrt_xmlrpc_params_add_string(&env, params, "product", product); - abrt_xmlrpc_params_add_string(&env, params, "component", component); - abrt_xmlrpc_params_add_string(&env, params, "version", version); -- abrt_xmlrpc_params_add_string(&env, params, "summary", (summary ? summary : bzsummary)); -+ abrt_xmlrpc_params_add_string(&env, params, "summary", summary); - abrt_xmlrpc_params_add_string(&env, params, "description", bzcomment); - abrt_xmlrpc_params_add_string(&env, params, "status_whiteboard", status_whiteboard); - -diff --git a/src/plugins/rhbz.h b/src/plugins/rhbz.h -index 976d333..2f91962 100644 ---- a/src/plugins/rhbz.h -+++ b/src/plugins/rhbz.h -@@ -101,8 +101,6 @@ int rhbz_attach_blob(struct abrt_xmlrpc *ax, const char *bug_id, - int rhbz_attach_fd(struct abrt_xmlrpc *ax, const char *bug_id, - const char *att_name, int fd, int flags); - --int is_comment_dup(GList *comments, const char *comment); -- - GList *rhbz_bug_cc(xmlrpc_value *result_xml); - - struct bug_info *rhbz_bug_info(struct abrt_xmlrpc *ax, int bug_id); --- -1.8.3.1 - diff --git a/SOURCES/0131-lib-allow-creating-root-owned-problem-directories-fr.patch b/SOURCES/0131-lib-allow-creating-root-owned-problem-directories-fr.patch new file mode 100644 index 0000000..ad2f4aa --- /dev/null +++ b/SOURCES/0131-lib-allow-creating-root-owned-problem-directories-fr.patch @@ -0,0 +1,109 @@ +From 41ec59db3e6e2f19adc128d8fbd4526976ee2ca2 Mon Sep 17 00:00:00 2001 +From: Jakub Filak +Date: Thu, 23 Apr 2015 16:33:00 +0200 +Subject: [LIBREPORT PATCH] lib: allow creating root owned problem directories + from problem data + +Without this patch libreport sets the owner of new problem directory +according to FILENAME_UID. This approach is not sufficient because ABRT +has introduced PrivateReports that should ensure that all problem +directories are owned by root. So ABRT needs a way to tell libreport to +create the new problem directory with uid=0. + +Signed-off-by: Jakub Filak +--- + src/include/problem_data.h | 1 + + src/lib/create_dump_dir.c | 47 +++++++++++++++++++++++++++------------------- + 2 files changed, 29 insertions(+), 19 deletions(-) + +diff --git a/src/include/problem_data.h b/src/include/problem_data.h +index 7a65d6c..02c945c 100644 +--- a/src/include/problem_data.h ++++ b/src/include/problem_data.h +@@ -131,6 +131,7 @@ problem_data_t *create_problem_data_for_reporting(const char *dump_dir_name); + @param base_dir_name Location to store the problem data + */ + struct dump_dir *create_dump_dir_from_problem_data(problem_data_t *problem_data, const char *base_dir_name); ++struct dump_dir *create_dump_dir_from_problem_data_ext(problem_data_t *problem_data, const char *base_dir_name, uid_t uid); + + #ifdef __cplusplus + } +diff --git a/src/lib/create_dump_dir.c b/src/lib/create_dump_dir.c +index 989a50c..45c248d 100644 +--- a/src/lib/create_dump_dir.c ++++ b/src/lib/create_dump_dir.c +@@ -30,7 +30,7 @@ static struct dump_dir *try_dd_create(const char *base_dir_name, const char *dir + return dd; + } + +-struct dump_dir *create_dump_dir_from_problem_data(problem_data_t *problem_data, const char *base_dir_name) ++struct dump_dir *create_dump_dir_from_problem_data_ext(problem_data_t *problem_data, const char *base_dir_name, uid_t uid) + { + INITIALIZE_LIBREPORT(); + +@@ -48,23 +48,8 @@ struct dump_dir *create_dump_dir_from_problem_data(problem_data_t *problem_data, + return NULL; + } + +- uid_t uid = (uid_t)-1L; +- char *uid_str = problem_data_get_content_or_NULL(problem_data, FILENAME_UID); +- +- if (uid_str) +- { +- char *endptr; +- errno = 0; +- long val = strtol(uid_str, &endptr, 10); +- +- if (errno != 0 || endptr == uid_str || *endptr != '\0' || INT_MAX < val) +- { +- error_msg(_("uid value is not valid: '%s'"), uid_str); +- return NULL; +- } +- +- uid = (uid_t)val; +- } ++ if (uid == (uid_t)-1L) ++ uid = 0; + + struct timeval tv; + if (gettimeofday(&tv, NULL) < 0) +@@ -139,7 +124,8 @@ struct dump_dir *create_dump_dir_from_problem_data(problem_data_t *problem_data, + * reporting from anaconda where we can't read /etc/{system,redhat}-release + * and os_release is taken from anaconda + */ +- dd_create_basic_files(dd, uid, NULL); ++ const uid_t crashed_uid = problem_data_get_content_or_NULL(problem_data, FILENAME_UID) == NULL ? uid : /*uid already saved*/-1; ++ dd_create_basic_files(dd, crashed_uid, NULL); + + problem_id[strlen(problem_id) - strlen(NEW_PD_SUFFIX)] = '\0'; + char* new_path = concat_path_file(base_dir_name, problem_id); +@@ -150,3 +136,26 @@ struct dump_dir *create_dump_dir_from_problem_data(problem_data_t *problem_data, + free(problem_id); + return dd; + } ++ ++struct dump_dir *create_dump_dir_from_problem_data(problem_data_t *problem_data, const char *base_dir_name) ++{ ++ uid_t uid = (uid_t)-1L; ++ char *uid_str = problem_data_get_content_or_NULL(problem_data, FILENAME_UID); ++ ++ if (uid_str) ++ { ++ char *endptr; ++ errno = 0; ++ long val = strtol(uid_str, &endptr, 10); ++ ++ if (errno != 0 || endptr == uid_str || *endptr != '\0' || INT_MAX < val) ++ { ++ error_msg(_("uid value is not valid: '%s'"), uid_str); ++ return NULL; ++ } ++ ++ uid = (uid_t)val; ++ } ++ ++ return create_dump_dir_from_problem_data_ext(problem_data, base_dir_name, uid); ++} +-- +1.8.3.1 + diff --git a/SOURCES/0132-lib-fix-races-in-dump-directory-handling-code.patch b/SOURCES/0132-lib-fix-races-in-dump-directory-handling-code.patch new file mode 100644 index 0000000..90cb008 --- /dev/null +++ b/SOURCES/0132-lib-fix-races-in-dump-directory-handling-code.patch @@ -0,0 +1,938 @@ +From 1951e7282043dfe1268d492aea056b554baedb75 Mon Sep 17 00:00:00 2001 +From: Jakub Filak +Date: Fri, 24 Apr 2015 10:57:23 +0200 +Subject: [LIBREPORT PATCH] lib: fix races in dump directory handling code + +Florian Weimer : + + dd_opendir() should keep a file handle (opened with O_DIRECTORY) and + use openat() and similar functions to access files in it. + + ... + + The file system manipulation functions should guard against hard + links (check that link count is <= 1, just as in the user coredump + code in abrt-hook-ccpp), possibly after opening the file + with O_PATH first to avoid side effects on open/close. + +Related: #1214745 + +Signed-off-by: Jakub Filak +--- + src/include/dump_dir.h | 7 + + src/include/internal_libreport.h | 4 + + src/lib/dump_dir.c | 417 +++++++++++++++++++++++---------------- + src/lib/problem_data.c | 6 +- + src/lib/xfuncs.c | 22 ++- + 5 files changed, 275 insertions(+), 181 deletions(-) + +diff --git a/src/include/dump_dir.h b/src/include/dump_dir.h +index 8f672d3..d675362 100644 +--- a/src/include/dump_dir.h ++++ b/src/include/dump_dir.h +@@ -34,6 +34,12 @@ extern "C" { + + /* Utility function */ + int create_symlink_lockfile(const char *filename, const char *pid_str); ++int create_symlink_lockfile_at(int dir_fd, const char *filename, const char *pid_str); ++ ++/* Opens filename for reading relatively to a directory represented by dir_fd. ++ * The function fails if the file is symbolic link, directory or hard link. ++ */ ++int secure_openat_read(int dir_fd, const char *filename); + + enum { + DD_FAIL_QUIETLY_ENOENT = (1 << 0), +@@ -57,6 +63,7 @@ struct dump_dir { + mode_t mode; + time_t dd_time; + char *dd_type; ++ int dd_fd; + }; + + void dd_close(struct dump_dir *dd); +diff --git a/src/include/internal_libreport.h b/src/include/internal_libreport.h +index 8d84fd4..d35d715 100644 +--- a/src/include/internal_libreport.h ++++ b/src/include/internal_libreport.h +@@ -406,6 +406,8 @@ int xopen3(const char *pathname, int flags, int mode); + int xopen(const char *pathname, int flags); + #define xunlink libreport_xunlink + void xunlink(const char *pathname); ++#define xunlinkat libreport_xunlinkat ++void xunlinkat(int dir_fd, const char *pathname, int flags); + + /* Just testing dent->d_type == DT_REG is wrong: some filesystems + * do not report the type, they report DT_UNKNOWN for every dirent +@@ -415,6 +417,8 @@ void xunlink(const char *pathname); + */ + #define is_regular_file libreport_is_regular_file + int is_regular_file(struct dirent *dent, const char *dirname); ++#define is_regular_file_at libreport_is_regular_file_at ++int is_regular_file_at(struct dirent *dent, int dir_fd); + + #define dot_or_dotdot libreport_dot_or_dotdot + bool dot_or_dotdot(const char *filename); +diff --git a/src/lib/dump_dir.c b/src/lib/dump_dir.c +index 0048faf..16cd987 100644 +--- a/src/lib/dump_dir.c ++++ b/src/lib/dump_dir.c +@@ -85,6 +85,7 @@ + + + static char *load_text_file(const char *path, unsigned flags); ++static char *load_text_file_at(int dir_fd, const char *name, unsigned flags); + static void copy_file_from_chroot(struct dump_dir* dd, const char *name, + const char *chroot_dir, const char *file_path); + +@@ -98,10 +99,10 @@ static bool isdigit_str(const char *str) + return true; + } + +-static bool exist_file_dir(const char *path) ++static bool exist_file_dir_at(int dir_fd, const char *name) + { + struct stat buf; +- if (stat(path, &buf) == 0) ++ if (fstatat(dir_fd, name, &buf, AT_SYMLINK_NOFOLLOW) == 0) + { + if (S_ISDIR(buf.st_mode) || S_ISREG(buf.st_mode)) + { +@@ -111,15 +112,61 @@ static bool exist_file_dir(const char *path) + return false; + } + ++/* Opens the file in the three following steps: ++ * 1. open the file with O_PATH (get a file descriptor for operations with ++ * inode) and O_NOFOLLOW (do not dereference symbolick links) ++ * 2. stat the resulting file descriptor and fail if the opened file is not a ++ * regular file or if the number of links is greater than 1 (that means that ++ * the inode has more names (hard links)) ++ * 3. "re-open" the file descriptor retrieved in the first step with O_RDONLY ++ * by opening /proc/self/fd/$fd (then close the former file descriptor and ++ * return the new one). ++ */ ++int secure_openat_read(int dir_fd, const char *pathname) ++{ ++ static char reopen_buf[sizeof("/proc/self/fd/") + 3*sizeof(int) + 1]; ++ ++ int path_fd = openat(dir_fd, pathname, O_PATH | O_NOFOLLOW); ++ if (path_fd < 0) ++ return -1; ++ ++ struct stat path_sb; ++ int r = fstat(path_fd, &path_sb); ++ if (r < 0) ++ { ++ perror_msg("stat"); ++ close(path_fd); ++ return -1; ++ } ++ ++ if (!S_ISREG(path_sb.st_mode) || path_sb.st_nlink > 1) ++ { ++ log_notice("Path isn't a regular file or has more links (%lu)", path_sb.st_nlink); ++ errno = EINVAL; ++ close(path_fd); ++ return -1; ++ } ++ ++ if (snprintf(reopen_buf, sizeof(reopen_buf), "/proc/self/fd/%d", path_fd) >= sizeof(reopen_buf)) { ++ error_msg("BUG: too long path to a file descriptor"); ++ abort(); ++ } ++ ++ const int fd = open(reopen_buf, O_RDONLY); ++ close(path_fd); ++ ++ return fd; ++} ++ + /* Returns value less than 0 if the file is not readable or + * if the file doesn't contain valid unixt time stamp. + * + * Any possible failure will be logged. + */ +-static time_t parse_time_file(const char *filename) ++static time_t parse_time_file_at(int dir_fd, const char *filename) + { + /* Open input file, and parse it. */ +- int fd = open(filename, O_RDONLY | O_NOFOLLOW); ++ int fd = secure_openat_read(dir_fd, filename); + if (fd < 0) + { + VERB2 pwarn_msg("Can't open '%s'", filename); +@@ -183,9 +230,9 @@ static time_t parse_time_file(const char *filename) + * 0: failed to lock (someone else has it locked) + * 1: success + */ +-int create_symlink_lockfile(const char* lock_file, const char* pid) ++int create_symlink_lockfile_at(int dir_fd, const char* lock_file, const char* pid) + { +- while (symlink(pid, lock_file) != 0) ++ while (symlinkat(pid, dir_fd, lock_file) != 0) + { + if (errno != EEXIST) + { +@@ -198,7 +245,7 @@ int create_symlink_lockfile(const char* lock_file, const char* pid) + } + + char pid_buf[sizeof(pid_t)*3 + 4]; +- ssize_t r = readlink(lock_file, pid_buf, sizeof(pid_buf) - 1); ++ ssize_t r = readlinkat(dir_fd, lock_file, pid_buf, sizeof(pid_buf) - 1); + if (r < 0) + { + if (errno == ENOENT) +@@ -230,7 +277,7 @@ int create_symlink_lockfile(const char* lock_file, const char* pid) + log("Lock file '%s' was locked by process %s, but it crashed?", lock_file, pid_buf); + } + /* The file may be deleted by now by other process. Ignore ENOENT */ +- if (unlink(lock_file) != 0 && errno != ENOENT) ++ if (unlinkat(dir_fd, lock_file, /*only files*/0) != 0 && errno != ENOENT) + { + perror_msg("Can't remove stale lock file '%s'", lock_file); + errno = 0; +@@ -242,21 +289,21 @@ int create_symlink_lockfile(const char* lock_file, const char* pid) + return 1; + } + ++int create_symlink_lockfile(const char *filename, const char *pid_str) ++{ ++ return create_symlink_lockfile_at(AT_FDCWD, filename, pid_str); ++} ++ + static const char *dd_check(struct dump_dir *dd) + { +- unsigned dirname_len = strlen(dd->dd_dirname); +- char filename_buf[FILENAME_MAX+1]; +- strcpy(filename_buf, dd->dd_dirname); +- strcpy(filename_buf + dirname_len, "/"FILENAME_TIME); +- dd->dd_time = parse_time_file(filename_buf); ++ dd->dd_time = parse_time_file_at(dd->dd_fd, FILENAME_TIME); + if (dd->dd_time < 0) + { + log_warning("Missing file: "FILENAME_TIME); + return FILENAME_TIME; + } + +- strcpy(filename_buf + dirname_len, "/"FILENAME_TYPE); +- dd->dd_type = load_text_file(filename_buf, DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE); ++ dd->dd_type = load_text_file_at(dd->dd_fd, FILENAME_TYPE, DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE); + if (!dd->dd_type || (strlen(dd->dd_type) == 0)) + { + log_warning("Missing or empty file: "FILENAME_TYPE); +@@ -274,17 +321,12 @@ static int dd_lock(struct dump_dir *dd, unsigned sleep_usec, int flags) + char pid_buf[sizeof(long)*3 + 2]; + snprintf(pid_buf, sizeof(pid_buf), "%lu", (long)getpid()); + +- unsigned dirname_len = strlen(dd->dd_dirname); +- char lock_buf[dirname_len + sizeof("/.lock")]; +- strcpy(lock_buf, dd->dd_dirname); +- strcpy(lock_buf + dirname_len, "/.lock"); +- + unsigned count = NO_TIME_FILE_COUNT; + + retry: + while (1) + { +- int r = create_symlink_lockfile(lock_buf, pid_buf); ++ int r = create_symlink_lockfile_at(dd->dd_fd, ".lock", pid_buf); + if (r < 0) + return r; /* error */ + if (r > 0) +@@ -304,8 +346,8 @@ static int dd_lock(struct dump_dir *dd, unsigned sleep_usec, int flags) + */ + if (missing_file) + { +- xunlink(lock_buf); +- log_warning("Unlocked '%s' (no or corrupted '%s' file)", lock_buf, missing_file); ++ xunlinkat(dd->dd_fd, ".lock", /*only files*/0); ++ log_warning("Unlocked '%s' (no or corrupted '%s' file)", dd->dd_dirname, missing_file); + if (--count == 0 || flags & DD_DONT_WAIT_FOR_LOCK) + { + errno = EISDIR; /* "this is an ordinary dir, not dump dir" */ +@@ -326,13 +368,9 @@ static void dd_unlock(struct dump_dir *dd) + { + dd->locked = 0; + +- unsigned dirname_len = strlen(dd->dd_dirname); +- char lock_buf[dirname_len + sizeof("/.lock")]; +- strcpy(lock_buf, dd->dd_dirname); +- strcpy(lock_buf + dirname_len, "/.lock"); +- xunlink(lock_buf); ++ xunlinkat(dd->dd_fd, ".lock", /*only files*/0); + +- log_info("Unlocked '%s'", lock_buf); ++ log_info("Unlocked '%s/.lock'", dd->dd_dirname); + } + } + +@@ -340,17 +378,16 @@ static inline struct dump_dir *dd_init(void) + { + struct dump_dir* dd = (struct dump_dir*)xzalloc(sizeof(struct dump_dir)); + dd->dd_time = -1; ++ dd->dd_fd = -1; + return dd; + } + +-int dd_exist(const struct dump_dir *dd, const char *path) ++int dd_exist(const struct dump_dir *dd, const char *name) + { +- if (!str_is_correct_filename(path)) +- error_msg_and_die("Cannot test existence. '%s' is not a valid file name", path); ++ if (!str_is_correct_filename(name)) ++ error_msg_and_die("Cannot test existence. '%s' is not a valid file name", name); + +- char *full_path = concat_path_file(dd->dd_dirname, path); +- int ret = exist_file_dir(full_path); +- free(full_path); ++ const int ret = exist_file_dir_at(dd->dd_fd, name); + return ret; + } + +@@ -360,6 +397,7 @@ void dd_close(struct dump_dir *dd) + return; + + dd_unlock(dd); ++ close(dd->dd_fd); + if (dd->next_dir) + { + closedir(dd->next_dir); +@@ -384,10 +422,13 @@ struct dump_dir *dd_opendir(const char *dir, int flags) + struct dump_dir *dd = dd_init(); + + dir = dd->dd_dirname = rm_trailing_slashes(dir); +- ++ dd->dd_fd = open(dir, O_DIRECTORY | O_NOFOLLOW); + struct stat stat_buf; +- if (stat(dir, &stat_buf) != 0) ++ if (dd->dd_fd < 0) + goto cant_access; ++ if (fstat(dd->dd_fd, &stat_buf) != 0) ++ goto cant_access; ++ + /* & 0666 should remove the executable bit */ + dd->mode = (stat_buf.st_mode & 0666); + +@@ -397,11 +438,12 @@ struct dump_dir *dd_opendir(const char *dir, int flags) + if ((flags & DD_OPEN_READONLY) && errno == EACCES) + { + /* Directory is not writable. If it seems to be readable, +- * return "read only" dd, not NULL */ +- if (stat(dir, &stat_buf) == 0 +- && S_ISDIR(stat_buf.st_mode) +- && access(dir, R_OK) == 0 +- ) { ++ * return "read only" dd, not NULL ++ * ++ * Does the directory have 'x' flag? ++ */ ++ if (faccessat(dd->dd_fd, ".", R_OK, AT_SYMLINK_NOFOLLOW) == 0) ++ { + if(dd_check(dd) != NULL) + { + dd_close(dd); +@@ -444,10 +486,9 @@ struct dump_dir *dd_opendir(const char *dir, int flags) + if (geteuid() == 0) + { + /* In case caller would want to create more files, he'll need uid:gid */ +- struct stat stat_buf; +- if (stat(dir, &stat_buf) != 0 || !S_ISDIR(stat_buf.st_mode)) ++ if (fstat(dd->dd_fd, &stat_buf) != 0) + { +- error_msg("Can't stat '%s', or it is not a directory", dir); ++ error_msg("Can't stat '%s'", dir); + dd_close(dd); + return NULL; + } +@@ -542,8 +583,7 @@ struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int + * dd_create("dir/..") and similar are madness, refuse them. + */ + error_msg("Bad dir name '%s'", dir); +- dd_close(dd); +- return NULL; ++ goto fail; + } + + /* Was creating it with mode 0700 and user as the owner, but this allows +@@ -559,22 +599,31 @@ struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int + if (r != 0) + { + perror_msg("Can't create directory '%s'", dir); +- dd_close(dd); +- return NULL; ++ goto fail; + } + +- if (dd_lock(dd, CREATE_LOCK_USLEEP, /*flags:*/ 0) < 0) ++ dd->dd_fd = open(dd->dd_dirname, O_DIRECTORY | O_NOFOLLOW); ++ if (dd->dd_fd < 0) + { +- dd_close(dd); +- return NULL; ++ perror_msg("Can't open newly created directory '%s'", dir); ++ goto fail; + } + ++ struct stat stat_sb; ++ if (fstat(dd->dd_fd, &stat_sb) < 0) ++ { ++ perror_msg("stat(%s)", dd->dd_dirname); ++ goto fail; ++ } ++ ++ if (dd_lock(dd, CREATE_LOCK_USLEEP, /*flags:*/ 0) < 0) ++ goto fail; ++ + /* mkdir's mode (above) can be affected by umask, fix it */ +- if (chmod(dir, dir_mode) == -1) ++ if (fchmod(dd->dd_fd, dir_mode) == -1) + { + perror_msg("Can't change mode of '%s'", dir); +- dd_close(dd); +- return NULL; ++ goto fail; + } + + dd->dd_uid = (uid_t)-1L; +@@ -616,6 +665,10 @@ struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int + } + + return dd; ++ ++fail: ++ dd_close(dd); ++ return NULL; + } + + /* Resets ownership of the given directory to UID and GID according to values +@@ -623,7 +676,7 @@ struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int + */ + int dd_reset_ownership(struct dump_dir *dd) + { +- const int r =lchown(dd->dd_dirname, dd->dd_uid, dd->dd_gid); ++ const int r = fchown(dd->dd_fd, dd->dd_uid, dd->dd_gid); + if (r < 0) + { + perror_msg("Can't change '%s' ownership to %lu:%lu", dd->dd_dirname, +@@ -740,59 +793,39 @@ void dd_sanitize_mode_and_owner(struct dump_dir *dd) + if (!dd->locked) + error_msg_and_die("dump_dir is not opened"); /* bug */ + +- DIR *d = opendir(dd->dd_dirname); +- if (!d) +- return; +- +- struct dirent *dent; +- while ((dent = readdir(d)) != NULL) ++ dd_init_next_file(dd); ++ char *short_name; ++ while (dd_get_next_file(dd, &short_name, /*full_name*/ NULL)) + { +- if (dent->d_name[0] == '.') /* ".lock", ".", ".."? skip */ +- continue; +- char *full_path = concat_path_file(dd->dd_dirname, dent->d_name); +- struct stat statbuf; +- if (lstat(full_path, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) +- { +- if ((statbuf.st_mode & 0777) != dd->mode) +- { +- /* We open the file only for fchmod() +- * +- * We use fchmod() because chmod() changes the permissions of +- * the file specified whose pathname is given in path, which +- * is dereferenced if it is a symbolic link. +- */ +- int fd = open(full_path, O_RDONLY | O_NOFOLLOW, dd->mode); +- if (fd >= 0) +- { +- if (fchmod(fd, dd->mode) != 0) +- { +- perror_msg("Can't change '%s' mode to 0%o", full_path, +- (unsigned)dd->mode); +- } +- close(fd); +- } +- else +- { +- perror_msg("Can't open regular file '%s'", full_path); +- } +- } +- if (statbuf.st_uid != dd->dd_uid || statbuf.st_gid != dd->dd_gid) +- { +- if (lchown(full_path, dd->dd_uid, dd->dd_gid) != 0) +- { +- perror_msg("Can't change '%s' ownership to %lu:%lu", full_path, +- (long)dd->dd_uid, (long)dd->dd_gid); +- } +- } +- } +- free(full_path); ++ /* The current process has to have read access at least */ ++ int fd = secure_openat_read(dd->dd_fd, short_name); ++ if (fd < 0) ++ goto next; ++ ++ if (fchmod(fd, dd->mode) != 0) ++ perror_msg("Can't change '%s/%s' mode to 0%o", dd->dd_dirname, short_name, ++ (unsigned)dd->mode); ++ ++ if (fchown(fd, dd->dd_uid, dd->dd_gid) != 0) ++ perror_msg("Can't change '%s/%s' ownership to %lu:%lu", dd->dd_dirname, short_name, ++ (long)dd->dd_uid, (long)dd->dd_gid); ++ ++ close(fd); ++next: ++ free(short_name); + } +- closedir(d); + } + +-static int delete_file_dir(const char *dir, bool skip_lock_file) ++static int delete_file_dir(int dir_fd, bool skip_lock_file) + { +- DIR *d = opendir(dir); ++ int opendir_fd = dup(dir_fd); ++ if (opendir_fd < 0) ++ { ++ perror_msg("delete_file_dir: dup(dir_fd)"); ++ return -1; ++ } ++ ++ DIR *d = fdopendir(opendir_fd); + if (!d) + { + /* The caller expects us to error out only if the directory +@@ -818,26 +851,35 @@ static int delete_file_dir(const char *dir, bool skip_lock_file) + unlink_lock_file = true; + continue; + } +- char *full_path = concat_path_file(dir, dent->d_name); +- if (unlink(full_path) == -1 && errno != ENOENT) ++ if (unlinkat(dir_fd, dent->d_name, /*only files*/0) == -1 && errno != ENOENT) + { + int err = 0; + if (errno == EISDIR) + { + errno = 0; +- err = delete_file_dir(full_path, /*skip_lock_file:*/ false); ++ int subdir_fd = openat(dir_fd, dent->d_name, O_DIRECTORY); ++ if (subdir_fd < 0) ++ { ++ perror_msg("Can't open sub-dir'%s'", dent->d_name); ++ closedir(d); ++ return -1; ++ } ++ else ++ { ++ err = delete_file_dir(subdir_fd, /*skip_lock_file:*/ false); ++ close(subdir_fd); ++ if (err == 0) ++ unlinkat(dir_fd, dent->d_name, AT_REMOVEDIR); ++ } + } + if (errno || err) + { +- perror_msg("Can't remove '%s'", full_path); +- free(full_path); ++ perror_msg("Can't remove '%s'", dent->d_name); + closedir(d); + return -1; + } + } +- free(full_path); + } +- closedir(d); + + /* Here we know for sure that all files/subdirs we found via readdir + * were deleted successfully. If rmdir below fails, we assume someone +@@ -845,29 +887,9 @@ static int delete_file_dir(const char *dir, bool skip_lock_file) + */ + + if (unlink_lock_file) +- { +- char *full_path = concat_path_file(dir, ".lock"); +- xunlink(full_path); +- free(full_path); +- +- unsigned cnt = RMDIR_FAIL_COUNT; +- do { +- if (rmdir(dir) == 0) +- return 0; +- /* Someone locked the dir after unlink, but before rmdir. +- * This "someone" must be dd_lock(). +- * It detects this (by seeing that there is no time file) +- * and backs off at once. So we need to just retry rmdir, +- * with minimal sleep. +- */ +- usleep(RMDIR_FAIL_USLEEP); +- } while (--cnt != 0); +- } ++ xunlinkat(dir_fd, ".lock", /*only files*/0); + +- int r = rmdir(dir); +- if (r) +- perror_msg("Can't remove directory '%s'", dir); +- return r; ++ return 0; + } + + int dd_delete(struct dump_dir *dd) +@@ -878,10 +900,34 @@ int dd_delete(struct dump_dir *dd) + return -1; + } + +- int r = delete_file_dir(dd->dd_dirname, /*skip_lock_file:*/ true); ++ if (delete_file_dir(dd->dd_fd, /*skip_lock_file:*/ true) != 0) ++ { ++ perror_msg("Can't remove contents of directory '%s'", dd->dd_dirname); ++ return -2; ++ } ++ ++ unsigned cnt = RMDIR_FAIL_COUNT; ++ do { ++ if (rmdir(dd->dd_dirname) == 0) ++ break; ++ /* Someone locked the dir after unlink, but before rmdir. ++ * This "someone" must be dd_lock(). ++ * It detects this (by seeing that there is no time file) ++ * and backs off at once. So we need to just retry rmdir, ++ * with minimal sleep. ++ */ ++ usleep(RMDIR_FAIL_USLEEP); ++ } while (--cnt != 0); ++ ++ if (cnt == 0) ++ { ++ perror_msg("Can't remove directory '%s'", dd->dd_dirname); ++ return -3; ++ } ++ + dd->locked = 0; /* delete_file_dir already removed .lock */ + dd_close(dd); +- return r; ++ return 0; + } + + int dd_chown(struct dump_dir *dd, uid_t new_uid) +@@ -911,29 +957,37 @@ int dd_chown(struct dump_dir *dd, uid_t new_uid) + gid_t groups_gid = pw->pw_gid; + #endif + +- int chown_res = lchown(dd->dd_dirname, owners_uid, groups_gid); ++ int chown_res = fchown(dd->dd_fd, owners_uid, groups_gid); + if (chown_res) +- perror_msg("lchown('%s')", dd->dd_dirname); ++ perror_msg("fchown('%s')", dd->dd_dirname); + else + { + dd_init_next_file(dd); +- char *full_name; +- while (chown_res == 0 && dd_get_next_file(dd, /*short_name*/ NULL, &full_name)) ++ char *short_name; ++ while (chown_res == 0 && dd_get_next_file(dd, &short_name, /*full_name*/ NULL)) + { +- log_debug("chowning %s", full_name); +- chown_res = lchown(full_name, owners_uid, groups_gid); ++ /* The current process has to have read access at least */ ++ int fd = secure_openat_read(dd->dd_fd, short_name); ++ if (fd < 0) ++ goto next; ++ ++ log_debug("chowning %s", short_name); ++ ++ chown_res = fchown(fd, owners_uid, groups_gid); + if (chown_res) +- perror_msg("lchown('%s')", full_name); +- free(full_name); ++ perror_msg("fchownat('%s')", short_name); ++ ++ close(fd); ++next: ++ free(short_name); + } + } + + return chown_res; + } + +-static char *load_text_file(const char *path, unsigned flags) ++static char *load_text_from_file_descriptor(int fd, const char *path, int flags) + { +- int fd = open(path, O_RDONLY | ((flags & DD_OPEN_FOLLOW) ? 0 : O_NOFOLLOW)); + if (fd == -1) + { + if (!(flags & DD_FAIL_QUIETLY_ENOENT)) +@@ -988,6 +1042,20 @@ static char *load_text_file(const char *path, unsigned flags) + return strbuf_free_nobuf(buf_content); + } + ++static char *load_text_file_at(int dir_fd, const char *name, unsigned flags) ++{ ++ assert(name[0] != '/'); ++ ++ const int fd = openat(dir_fd, name, O_RDONLY | ((flags & DD_OPEN_FOLLOW) ? 0 : O_NOFOLLOW)); ++ return load_text_from_file_descriptor(fd, name, flags); ++} ++ ++static char *load_text_file(const char *path, unsigned flags) ++{ ++ const int fd = open(path, O_RDONLY | ((flags & DD_OPEN_FOLLOW) ? 0 : O_NOFOLLOW)); ++ return load_text_from_file_descriptor(fd, path, flags); ++} ++ + static void copy_file_from_chroot(struct dump_dir* dd, const char *name, const char *chroot_dir, const char *file_path) + { + char *chrooted_name = concat_path_file(chroot_dir, file_path); +@@ -1001,14 +1069,16 @@ static void copy_file_from_chroot(struct dump_dir* dd, const char *name, const c + } + } + +-static bool save_binary_file(const char *path, const char* data, unsigned size, uid_t uid, gid_t gid, mode_t mode) ++static bool save_binary_file_at(int dir_fd, const char *name, const char* data, unsigned size, uid_t uid, gid_t gid, mode_t mode) + { ++ assert(name[0] != '/'); ++ + /* the mode is set by the caller, see dd_create() for security analysis */ +- unlink(path); +- int fd = open(path, O_WRONLY | O_TRUNC | O_CREAT | O_NOFOLLOW, mode); ++ unlinkat(dir_fd, name, /*remove only files*/0); ++ int fd = openat(dir_fd, name, O_WRONLY | O_EXCL | O_CREAT | O_NOFOLLOW, mode); + if (fd < 0) + { +- perror_msg("Can't open file '%s'", path); ++ perror_msg("Can't open file '%s'", name); + return false; + } + +@@ -1016,7 +1086,9 @@ static bool save_binary_file(const char *path, const char* data, unsigned size, + { + if (fchown(fd, uid, gid) == -1) + { +- perror_msg("Can't change '%s' ownership to %lu:%lu", path, (long)uid, (long)gid); ++ perror_msg("Can't change '%s' ownership to %lu:%lu", name, (long)uid, (long)gid); ++ close(fd); ++ return false; + } + } + +@@ -1028,14 +1100,16 @@ static bool save_binary_file(const char *path, const char* data, unsigned size, + */ + if (fchmod(fd, mode) == -1) + { +- perror_msg("Can't change mode of '%s'", path); ++ perror_msg("Can't change mode of '%s'", name); ++ close(fd); ++ return false; + } + + unsigned r = full_write(fd, data, size); + close(fd); + if (r != size) + { +- error_msg("Can't save file '%s'", path); ++ error_msg("Can't save file '%s'", name); + return false; + } + +@@ -1058,11 +1132,7 @@ char* dd_load_text_ext(const struct dump_dir *dd, const char *name, unsigned fla + if (strcmp(name, "release") == 0) + name = FILENAME_OS_RELEASE; + +- char *full_path = concat_path_file(dd->dd_dirname, name); +- char *ret = load_text_file(full_path, flags); +- free(full_path); +- +- return ret; ++ return load_text_file_at(dd->dd_fd, name, flags); + } + + char* dd_load_text(const struct dump_dir *dd, const char *name) +@@ -1078,9 +1148,7 @@ void dd_save_text(struct dump_dir *dd, const char *name, const char *data) + if (!str_is_correct_filename(name)) + error_msg_and_die("Cannot save text. '%s' is not a valid file name", name); + +- char *full_path = concat_path_file(dd->dd_dirname, name); +- save_binary_file(full_path, data, strlen(data), dd->dd_uid, dd->dd_gid, dd->mode); +- free(full_path); ++ save_binary_file_at(dd->dd_fd, name, data, strlen(data), dd->dd_uid, dd->dd_gid, dd->mode); + } + + void dd_save_binary(struct dump_dir* dd, const char* name, const char* data, unsigned size) +@@ -1091,9 +1159,7 @@ void dd_save_binary(struct dump_dir* dd, const char* name, const char* data, uns + if (!str_is_correct_filename(name)) + error_msg_and_die("Cannot save binary. '%s' is not a valid file name", name); + +- char *full_path = concat_path_file(dd->dd_dirname, name); +- save_binary_file(full_path, data, size, dd->dd_uid, dd->dd_gid, dd->mode); +- free(full_path); ++ save_binary_file_at(dd->dd_fd, name, data, size, dd->dd_uid, dd->dd_gid, dd->mode); + } + + long dd_get_item_size(struct dump_dir *dd, const char *name) +@@ -1102,21 +1168,19 @@ long dd_get_item_size(struct dump_dir *dd, const char *name) + error_msg_and_die("Cannot get item size. '%s' is not a valid file name", name); + + long size = -1; +- char *iname = concat_path_file(dd->dd_dirname, name); + struct stat statbuf; ++ int r = fstatat(dd->dd_fd, name, &statbuf, AT_SYMLINK_NOFOLLOW); + +- if (lstat(iname, &statbuf) == 0 && S_ISREG(statbuf.st_mode)) ++ if (r == 0 && S_ISREG(statbuf.st_mode)) + size = statbuf.st_size; + else + { + if (errno == ENOENT) + size = 0; + else +- perror_msg("Can't get size of file '%s'", iname); ++ perror_msg("Can't get size of file '%s'", name); + } + +- free(iname); +- + return size; + } + +@@ -1128,18 +1192,16 @@ int dd_delete_item(struct dump_dir *dd, const char *name) + if (!str_is_correct_filename(name)) + error_msg_and_die("Cannot delete item. '%s' is not a valid file name", name); + +- char *path = concat_path_file(dd->dd_dirname, name); +- int res = unlink(path); ++ int res = unlinkat(dd->dd_fd, name, /*only files*/0); + + if (res < 0) + { + if (errno == ENOENT) + errno = res = 0; + else +- perror_msg("Can't delete file '%s'", path); ++ perror_msg("Can't delete file '%s'", name); + } + +- free(path); + return res; + } + +@@ -1147,11 +1209,17 @@ DIR *dd_init_next_file(struct dump_dir *dd) + { + // if (!dd->locked) + // error_msg_and_die("dump_dir is not opened"); /* bug */ ++ int opendir_fd = dup(dd->dd_fd); ++ if (opendir_fd < 0) ++ { ++ perror_msg("dd_init_next_file: dup(dd_fd)"); ++ return NULL; ++ } + + if (dd->next_dir) + closedir(dd->next_dir); + +- dd->next_dir = opendir(dd->dd_dirname); ++ dd->next_dir = fdopendir(opendir_fd); + if (!dd->next_dir) + { + error_msg("Can't open directory '%s'", dd->dd_dirname); +@@ -1168,7 +1236,7 @@ int dd_get_next_file(struct dump_dir *dd, char **short_name, char **full_name) + struct dirent *dent; + while ((dent = readdir(dd->next_dir)) != NULL) + { +- if (is_regular_file(dent, dd->dd_dirname)) ++ if (is_regular_file_at(dent, dd->dd_fd)) + { + if (short_name) + *short_name = xstrdup(dent->d_name); +@@ -1233,6 +1301,7 @@ int dd_rename(struct dump_dir *dd, const char *new_path) + return -1; + } + ++ /* Keeps the opened file descriptor valid */ + int res = rename(dd->dd_dirname, new_path); + if (res == 0) + { +diff --git a/src/lib/problem_data.c b/src/lib/problem_data.c +index fc07288..ebddd3c 100644 +--- a/src/lib/problem_data.c ++++ b/src/lib/problem_data.c +@@ -279,14 +279,14 @@ static const char *const always_text_files[] = { + FILENAME_OS_RELEASE, + NULL + }; +-static char* is_text_file(const char *name, ssize_t *sz) ++static char* is_text_file_at(int dir_fd, const char *name, ssize_t *sz) + { + /* We were using magic.h API to check for file being text, but it thinks + * that file containing just "0" is not text (!!) + * So, we do it ourself. + */ + +- int fd = open(name, O_RDONLY); ++ int fd = secure_openat_read(dir_fd, name); + if (fd < 0) + return NULL; /* it's not text (because it does not exist! :) */ + +@@ -399,7 +399,7 @@ void problem_data_load_from_dump_dir(problem_data_t *problem_data, struct dump_d + } + + ssize_t sz = 4*1024; +- char *text = is_text_file(full_name, &sz); ++ char *text = is_text_file_at(dd->dd_fd, short_name, &sz); + if (!text || text == HUGE_TEXT) + { + int flag = !text ? CD_FLAG_BIN : (CD_FLAG_BIN+CD_FLAG_BIGTXT); +diff --git a/src/lib/xfuncs.c b/src/lib/xfuncs.c +index 1ce44aa..979c7b8 100644 +--- a/src/lib/xfuncs.c ++++ b/src/lib/xfuncs.c +@@ -331,6 +331,12 @@ int xopen(const char *pathname, int flags) + return xopen3(pathname, flags, 0666); + } + ++void xunlinkat(int dir_fd, const char *pathname, int flags) ++{ ++ if (unlinkat(dir_fd, pathname, flags)) ++ perror_msg_and_die("Can't remove file '%s'", pathname); ++} ++ + void xunlink(const char *pathname) + { + if (unlink(pathname)) +@@ -359,21 +365,29 @@ int open_or_warn(const char *pathname, int flags) + * do not report the type, they report DT_UNKNOWN for every dirent + * (and this is not a bug in filesystem, this is allowed by standards). + */ +-int is_regular_file(struct dirent *dent, const char *dirname) ++int is_regular_file_at(struct dirent *dent, int dir_fd) + { + if (dent->d_type == DT_REG) + return 1; + if (dent->d_type != DT_UNKNOWN) + return 0; + +- char *fullname = xasprintf("%s/%s", dirname, dent->d_name); + struct stat statbuf; +- int r = lstat(fullname, &statbuf); +- free(fullname); ++ int r = fstatat(dir_fd, dent->d_name, &statbuf, AT_SYMLINK_NOFOLLOW); + + return r == 0 && S_ISREG(statbuf.st_mode); + } + ++int is_regular_file(struct dirent *dent, const char *dirname) ++{ ++ int dir_fd = open(dirname, O_DIRECTORY); ++ if (dir_fd < 0) ++ return 0; ++ int r = is_regular_file_at(dent, dir_fd); ++ close(dir_fd); ++ return r; ++} ++ + /* Is it "." or ".."? */ + /* abrtlib candidate */ + bool dot_or_dotdot(const char *filename) +-- +1.8.3.1 + diff --git a/SOURCES/0133-lib-add-alternative-dd-functions-accepting-fds.patch b/SOURCES/0133-lib-add-alternative-dd-functions-accepting-fds.patch new file mode 100644 index 0000000..52afe77 --- /dev/null +++ b/SOURCES/0133-lib-add-alternative-dd-functions-accepting-fds.patch @@ -0,0 +1,228 @@ +From 16b2ebdb678b7475bacb80dd59e949055d3f856c Mon Sep 17 00:00:00 2001 +From: Jakub Filak +Date: Fri, 24 Apr 2015 11:42:18 +0200 +Subject: [LIBREPORT PATCH] lib: add alternative dd functions accepting fds + +Florian Weimer : + + dump_dir_accessible_by_uid() is fundamentally insecure because it + opens up a classic time-of-check-time-of-use race between this + function and and dd_opendir(). At least re-checking after + dd_opendir() with the stored file descriptor is needed. + +Related: #1214745 + +Signed-off-by: Jakub Filak +--- + src/include/dump_dir.h | 8 +++++ + src/lib/dump_dir.c | 82 +++++++++++++++++++++++++++++++++++++++++--------- + 2 files changed, 75 insertions(+), 15 deletions(-) + +diff --git a/src/include/dump_dir.h b/src/include/dump_dir.h +index d675362..07b119a 100644 +--- a/src/include/dump_dir.h ++++ b/src/include/dump_dir.h +@@ -68,7 +68,13 @@ struct dump_dir { + + void dd_close(struct dump_dir *dd); + ++/* Opens the given path and returns the resulting file descriptor. ++ */ ++int dd_openfd(const char *dir); + struct dump_dir *dd_opendir(const char *dir, int flags); ++/* Skips dd_openfd(dir) and uses the given file descriptor instead ++ */ ++struct dump_dir *dd_fdopendir(int dir_fd, const char *dir, int flags); + struct dump_dir *dd_create_skeleton(const char *dir, uid_t uid, mode_t mode, int flags); + int dd_reset_ownership(struct dump_dir *dd); + /* Pass uid = (uid_t)-1L to disable chown'ing of newly created files +@@ -147,6 +153,7 @@ void delete_dump_dir(const char *dirname); + * Returns non zero if dump dir is accessible otherwise return 0 value. + */ + int dump_dir_accessible_by_uid(const char *dirname, uid_t uid); ++int fdump_dir_accessible_by_uid(int dir_fd, uid_t uid); + + enum { + DD_STAT_ACCESSIBLE_BY_UID = 1, +@@ -161,6 +168,7 @@ enum { + * Returns negative number if error occurred otherwise returns 0 or positive number. + */ + int dump_dir_stat_for_uid(const char *dirname, uid_t uid); ++int fdump_dir_stat_for_uid(int dir_fd, uid_t uid); + + /* creates not_reportable file in the problem directory and saves the + reason to it, which prevents libreport from reporting the problem +diff --git a/src/lib/dump_dir.c b/src/lib/dump_dir.c +index 16cd987..a0e96e4 100644 +--- a/src/lib/dump_dir.c ++++ b/src/lib/dump_dir.c +@@ -417,12 +417,8 @@ static char* rm_trailing_slashes(const char *dir) + return xstrndup(dir, len); + } + +-struct dump_dir *dd_opendir(const char *dir, int flags) ++static struct dump_dir *dd_do_open(struct dump_dir *dd, int flags) + { +- struct dump_dir *dd = dd_init(); +- +- dir = dd->dd_dirname = rm_trailing_slashes(dir); +- dd->dd_fd = open(dir, O_DIRECTORY | O_NOFOLLOW); + struct stat stat_buf; + if (dd->dd_fd < 0) + goto cant_access; +@@ -440,6 +436,7 @@ struct dump_dir *dd_opendir(const char *dir, int flags) + /* Directory is not writable. If it seems to be readable, + * return "read only" dd, not NULL + * ++ * + * Does the directory have 'x' flag? + */ + if (faccessat(dd->dd_fd, ".", R_OK, AT_SYMLINK_NOFOLLOW) == 0) +@@ -461,7 +458,7 @@ struct dump_dir *dd_opendir(const char *dir, int flags) + * directory when run without arguments, because its option -d DIR + * defaults to "."! + */ +- error_msg("'%s' is not a problem directory", dir); ++ error_msg("'%s' is not a problem directory", dd->dd_dirname); + } + else + { +@@ -469,12 +466,12 @@ struct dump_dir *dd_opendir(const char *dir, int flags) + if (errno == ENOENT || errno == ENOTDIR) + { + if (!(flags & DD_FAIL_QUIETLY_ENOENT)) +- error_msg("'%s' does not exist", dir); ++ error_msg("'%s' does not exist", dd->dd_dirname); + } + else + { + if (!(flags & DD_FAIL_QUIETLY_EACCES)) +- perror_msg("Can't access '%s'", dir); ++ perror_msg("Can't access '%s'", dd->dd_dirname); + } + } + dd_close(dd); +@@ -488,7 +485,7 @@ struct dump_dir *dd_opendir(const char *dir, int flags) + /* In case caller would want to create more files, he'll need uid:gid */ + if (fstat(dd->dd_fd, &stat_buf) != 0) + { +- error_msg("Can't stat '%s'", dir); ++ error_msg("Can't stat '%s'", dd->dd_dirname); + dd_close(dd); + return NULL; + } +@@ -499,6 +496,34 @@ struct dump_dir *dd_opendir(const char *dir, int flags) + return dd; + } + ++int dd_openfd(const char *dir) ++{ ++ return open(dir, O_DIRECTORY | O_NOFOLLOW); ++} ++ ++struct dump_dir *dd_fdopendir(int dir_fd, const char *dir, int flags) ++{ ++ struct dump_dir *dd = dd_init(); ++ ++ dd->dd_dirname = rm_trailing_slashes(dir); ++ dd->dd_fd = dir_fd; ++ ++ /* Do not interpret errno in dd_do_open() if dd_fd < 0 */ ++ errno = 0; ++ return dd_do_open(dd, flags); ++} ++ ++struct dump_dir *dd_opendir(const char *dir, int flags) ++{ ++ struct dump_dir *dd = dd_init(); ++ ++ dd->dd_dirname = rm_trailing_slashes(dir); ++ /* dd_do_open validates dd_fd */ ++ dd->dd_fd = dd_openfd(dir); ++ ++ return dd_do_open(dd, flags); ++} ++ + /* Create a fresh empty debug dump dir which is owned bu the calling user. If + * you want to create the directory with meaningful ownership you should + * consider using dd_create() function or you can modify the ownership +@@ -936,7 +961,7 @@ int dd_chown(struct dump_dir *dd, uid_t new_uid) + error_msg_and_die("dump_dir is not opened"); /* bug */ + + struct stat statbuf; +- if (!(stat(dd->dd_dirname, &statbuf) == 0 && S_ISDIR(statbuf.st_mode))) ++ if (!fstat(dd->dd_fd, &statbuf) == 0) + { + perror_msg("stat('%s')", dd->dd_dirname); + return 1; +@@ -1352,12 +1377,12 @@ static bool uid_in_group(uid_t uid, gid_t gid) + } + #endif + +-int dump_dir_stat_for_uid(const char *dirname, uid_t uid) ++int fdump_dir_stat_for_uid(int dir_fd, uid_t uid) + { + struct stat statbuf; +- if (stat(dirname, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode)) ++ if (fstat(dir_fd, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode)) + { +- log_debug("can't get stat of '%s': not a problem directory", dirname); ++ log_debug("can't get stat: not a problem directory"); + errno = ENOTDIR; + return -1; + } +@@ -1367,7 +1392,7 @@ int dump_dir_stat_for_uid(const char *dirname, uid_t uid) + int ddstat = 0; + if (uid == 0 || (statbuf.st_mode & S_IROTH)) + { +- log_debug("directory '%s' is accessible by %ld uid", dirname, (long)uid); ++ log_debug("directory is accessible by %ld uid", (long)uid); + ddstat |= DD_STAT_ACCESSIBLE_BY_UID; + } + +@@ -1377,7 +1402,7 @@ int dump_dir_stat_for_uid(const char *dirname, uid_t uid) + if (uid_in_group(uid, statbuf.st_gid)) + #endif + { +- log_debug("%ld uid owns directory '%s'", (long)uid, dirname); ++ log_debug("%ld uid owns directory", (long)uid); + ddstat |= DD_STAT_ACCESSIBLE_BY_UID; + ddstat |= DD_STAT_OWNED_BY_UID; + } +@@ -1385,6 +1410,33 @@ int dump_dir_stat_for_uid(const char *dirname, uid_t uid) + return ddstat; + } + ++int dump_dir_stat_for_uid(const char *dirname, uid_t uid) ++{ ++ int dir_fd = open(dirname, O_DIRECTORY | O_NOFOLLOW); ++ if (dir_fd < 0) ++ { ++ log_debug("can't open '%s': not a problem directory", dirname); ++ errno = ENOTDIR; ++ return -1; ++ } ++ ++ int r = fdump_dir_stat_for_uid(dir_fd, uid); ++ close(dir_fd); ++ return r; ++} ++ ++int fdump_dir_accessible_by_uid(int dir_fd, uid_t uid) ++{ ++ int ddstat = fdump_dir_stat_for_uid(dir_fd, uid); ++ ++ if (ddstat >= 0) ++ return ddstat & DD_STAT_ACCESSIBLE_BY_UID; ++ ++ VERB3 pwarn_msg("can't determine accessibility for %ld uid", (long)uid); ++ ++ return 0; ++} ++ + int dump_dir_accessible_by_uid(const char *dirname, uid_t uid) + { + int ddstat = dump_dir_stat_for_uid(dirname, uid); +-- +1.8.3.1 + diff --git a/SOURCES/0133-ureport-set-url-to-public-faf-server.patch b/SOURCES/0133-ureport-set-url-to-public-faf-server.patch deleted file mode 100644 index 0d1e8f6..0000000 --- a/SOURCES/0133-ureport-set-url-to-public-faf-server.patch +++ /dev/null @@ -1,27 +0,0 @@ -From 9f11449cd89e87b932bf47e264e00f36582b3a9b Mon Sep 17 00:00:00 2001 -From: Matej Habrnal -Date: Mon, 2 Feb 2015 16:31:51 +0100 -Subject: [PATCH 133/136] ureport: set url to public faf server - -Set url to public faf server because the private one doesn't return URL to -known issues, bthash, possible solution etc. - -Signed-off-by: Matej Habrnal ---- - src/plugins/ureport.conf | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/plugins/ureport.conf b/src/plugins/ureport.conf -index e04bf56..4d64ee7 100644 ---- a/src/plugins/ureport.conf -+++ b/src/plugins/ureport.conf -@@ -1,5 +1,5 @@ - # Base URL to uReport server --# URL = http://bug-report.itos.redhat.com -+URL = https://retrace.fedoraproject.org/faf - - # no means that ssl certificates will not be checked - # SSLVerify = no --- -1.8.3.1 - diff --git a/SOURCES/0134-build-switch-the-default-dump-dir-mode-to-0640.patch b/SOURCES/0134-build-switch-the-default-dump-dir-mode-to-0640.patch new file mode 100644 index 0000000..44e26d5 --- /dev/null +++ b/SOURCES/0134-build-switch-the-default-dump-dir-mode-to-0640.patch @@ -0,0 +1,36 @@ +From c962918bc70a61a8cc647898ee8b1ff1c14a87c5 Mon Sep 17 00:00:00 2001 +From: Jakub Filak +Date: Tue, 28 Apr 2015 12:49:38 +0200 +Subject: [LIBREPORT PATCH] build: switch the default dump dir mode to 0640 + +The 0660 allows root escalations in ABRT. We don't really need to have +the dump directories writable for the group as ABRT processes run under +root. We introduced 0x1 for group with the switch to /var/tmp/abrt +because we thought that we will have ABRT processes run under the user +abrt, but there are no signs that we will ever pursue such a setup. + +Related: #1212861 + +Signed-off-by: Jakub Filak +--- + configure.ac | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/configure.ac b/configure.ac +index df88240..8aea410 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -249,8 +249,8 @@ AC_PATH_PROG(AUGPARSE, augparse, no) + + AC_ARG_WITH([defaultdumpdirmode], + AS_HELP_STRING([--with-defaultdumpdirmode=OCTAL-MODE], +- [Default dump dir mode (default: 0660)]), +- [], [with_defaultdumpdirmode="0660"]) ++ [Default dump dir mode (default: 0640)]), ++ [], [with_defaultdumpdirmode="0640"]) + AC_SUBST([DEFAULT_DUMP_DIR_MODE], [$with_defaultdumpdirmode]) + + DUMP_DIR_OWNED_BY_USER=1 +-- +1.8.3.1 + diff --git a/SOURCES/0134-conf-changed-URL-for-sending-uReport.patch b/SOURCES/0134-conf-changed-URL-for-sending-uReport.patch deleted file mode 100644 index 424f5ef..0000000 --- a/SOURCES/0134-conf-changed-URL-for-sending-uReport.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 8d81afa9fdd7beeac1f4fa20073af599713e071e Mon Sep 17 00:00:00 2001 -From: Matej Habrnal -Date: Mon, 2 Feb 2015 21:41:36 +0100 -Subject: [PATCH 134/136] conf: changed URL for sending uReport - -Changed faf server url in report_uReport.xml.in. -uReports are sending to https://retrace.fedoraproject.org/faf by default. - -Signed-off-by: Matej Habrnal ---- - src/plugins/report_uReport.xml.in | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/src/plugins/report_uReport.xml.in b/src/plugins/report_uReport.xml.in -index 63dfc22..aca857a 100644 ---- a/src/plugins/report_uReport.xml.in -+++ b/src/plugins/report_uReport.xml.in -@@ -11,7 +11,7 @@ - <_label>uReport Server URL - no - <_description>Address of uReport webservice -- http://bug-report.itos.redhat.com -+ https://retrace.fedoraproject.org/faf - -