diff --git before/doc/aide.1 after/doc/aide.1 index c68335f..4737f73 100644 --- before/doc/aide.1 +++ after/doc/aide.1 @@ -130,12 +130,25 @@ SIGUSR1 toggles the log_level between current and debug level. .PP .SH NOTES +.IP "Checksum encoding" + The checksums in the database and in the output are by default base64 encoded (see also report_base16 option). To decode them you can use the following shell command: echo | base64 \-d | hexdump \-v \-e '32/1 "%02x" "\\n"' +.IP "Control characters" + +Control characters (00-31 and 127) are always escaped in log and plain report +output. They are escaped by a literal backslash (\\) followed by exactly 3 +digits representing the character in octal notation (e.g. a newline is output +as "\fB\\012\fR"). A literal backslash is not escaped unless it is followed by +3 digits (0-9), in this case the literal backslash is escaped as +"\fB\\134\fR". Reports in JSON format are escaped according to the JSON specs +(e.g. a newline is output as "\fB\\b\fR" or an escape (\fBESC\fR) is output as +"\fB\\u001b\fR") + .PP .SH FILES diff --git before/include/util.h after/include/util.h index 897ac64..c22c9d6 100644 --- before/include/util.h +++ after/include/util.h @@ -57,6 +57,9 @@ int cmpurl(url_t*, url_t*); int contains_unsafe(const char*); +char *strnesc(const char *, size_t); +char *stresc(const char *); + void decode_string(char*); char* encode_string(const char*); diff --git before/src/aide.c after/src/aide.c index f9821f4..aa8a569 100644 --- before/src/aide.c +++ after/src/aide.c @@ -282,7 +282,8 @@ static void read_param(int argc,char**argv) if((conf->limit_crx=pcre2_compile((PCRE2_SPTR) conf->limit, PCRE2_ZERO_TERMINATED, PCRE2_UTF|PCRE2_ANCHORED, &pcre2_errorcode, &pcre2_erroffset, NULL)) == NULL) { PCRE2_UCHAR pcre2_error[128]; pcre2_get_error_message(pcre2_errorcode, pcre2_error, 128); - INVALID_ARGUMENT("--limit", error in regular expression '%s' at %zu: %s, conf->limit, pcre2_erroffset, pcre2_error) + char * limit_safe = stresc(conf->limit); + INVALID_ARGUMENT("--limit", error in regular expression '%s' at %zu: %s, limit_safe, pcre2_erroffset, pcre2_error) } conf->limit_md = pcre2_match_data_create_from_pattern(conf->limit_crx, NULL); diff --git before/src/gen_list.c after/src/gen_list.c index 9561443..729dff3 100644 --- before/src/gen_list.c +++ after/src/gen_list.c @@ -339,35 +339,39 @@ void print_match(char* filename, rx_rule *rule, match_result match, RESTRICTION_ char * str; char* attr_str; char file_type = get_restriction_char(restriction); + char *filename_safe = stresc(filename); switch (match) { case RESULT_SELECTIVE_MATCH: str = get_restriction_string(rule->restriction); attr_str = diff_attributes(0, rule->attr); - fprintf(stdout, "[X] %c '%s': selective rule: '%s %s %s' (%s:%d: '%s%s%s')\n", file_type, filename, rule->rx, str, attr_str, rule->config_filename, rule->config_linenumber, rule->config_line, rule->prefix?"', prefix: '":"", rule->prefix?rule->prefix:""); + fprintf(stdout, "[X] %c '%s': selective rule: '%s %s %s' (%s:%d: '%s%s%s')\n", file_type, filename_safe, rule->rx, str, attr_str, rule->config_filename, rule->config_linenumber, rule->config_line, rule->prefix?"', prefix: '":"", rule->prefix?rule->prefix:""); free(attr_str); free(str); break; case RESULT_EQUAL_MATCH: str = get_restriction_string(rule->restriction); attr_str = diff_attributes(0, rule->attr); - fprintf(stdout, "[X] %c '%s': equal rule: '=%s %s %s' (%s:%d: '%s%s%s')\n", file_type, filename, rule->rx, str, attr_str, rule->config_filename, rule->config_linenumber, rule->config_line, rule->prefix?"', prefix: '":"", rule->prefix?rule->prefix:""); + fprintf(stdout, "[X] %c '%s': equal rule: '=%s %s %s' (%s:%d: '%s%s%s')\n", file_type, filename_safe, rule->rx, str, attr_str, rule->config_filename, rule->config_linenumber, rule->config_line, rule->prefix?"', prefix: '":"", rule->prefix?rule->prefix:""); free(attr_str); free(str); break; case RESULT_PARTIAL_MATCH: case RESULT_NO_MATCH: if (rule) { - fprintf(stdout, "[ ] %c '%s': negative rule: '!%s %s' (%s:%d: '%s%s%s')\n", file_type, filename, rule->rx, str = get_restriction_string(rule->restriction), rule->config_filename, rule->config_linenumber, rule->config_line, rule->prefix?"', prefix: '":"", rule->prefix?rule->prefix:""); + fprintf(stdout, "[ ] %c '%s': negative rule: '!%s %s' (%s:%d: '%s%s%s')\n", file_type, filename_safe, rule->rx, str = get_restriction_string(rule->restriction), rule->config_filename, rule->config_linenumber, rule->config_line, rule->prefix?"', prefix: '":"", rule->prefix?rule->prefix:""); free(str); } else { - fprintf(stdout, "[ ] %c '%s': no matching rule\n", file_type, filename); + fprintf(stdout, "[ ] %c '%s': no matching rule\n", file_type, filename_safe); } break; case RESULT_PARTIAL_LIMIT_MATCH: case RESULT_NO_LIMIT_MATCH: - fprintf(stdout, "[ ] %c '%s': outside of limit '%s'\n", file_type, filename, conf->limit); + str = stresc(conf->limit); + fprintf(stdout, "[ ] %c '%s': outside of limit '%s'\n", file_type, filename_safe, str); + free(str); break; } + free(filename_safe); } /* diff --git before/src/log.c after/src/log.c index 3f741a6..ce5417c 100644 --- before/src/log.c +++ after/src/log.c @@ -30,6 +30,7 @@ #include "log.h" #include "errorcodes.h" +#include "util.h" LOG_LEVEL prev_log_level = LOG_LEVEL_UNSET; LOG_LEVEL log_level = LOG_LEVEL_UNSET; @@ -118,7 +119,9 @@ static void log_cached_lines(void) { for(int i = 0; i < ncachedlines; ++i) { LOG_LEVEL level = cached_lines[i].level; if (level == LOG_LEVEL_ERROR || level <= log_level) { - fprintf(url, "%s: %s\n", log_level_array[level-1].log_string, cached_lines[i].message); + char * msg_safe = stresc(cached_lines[i].message); + fprintf(url, "%s: %s\n", log_level_array[level-1].log_string, msg_safe); + free(msg_safe); } free(cached_lines[i].message); } @@ -135,9 +138,24 @@ static void vlog_msg(LOG_LEVEL level,const char* format, va_list ap) { FILE* url = stderr; if (level == LOG_LEVEL_ERROR || level <= log_level) { - fprintf(url, "%s: ", log_level_array[level-1].log_string ); - vfprintf(url, format, ap); - fprintf(url, "\n"); + + va_list aq; + va_copy(aq, ap); + size_t n = vsnprintf(NULL, 0, format, aq) + 1; + va_end(aq); + + int size = n * sizeof(char); + char *msg_unsafe = malloc(size); + if (msg_unsafe == NULL) { + fprintf(stderr, "%s: malloc: failed to allocate %d bytes of memory\n", log_level_array[LOG_LEVEL_ERROR-1].log_string, size); + exit(MEMORY_ALLOCATION_FAILURE); + } + + vsnprintf(msg_unsafe, n, format, ap); + char *msg_safe = stresc(msg_unsafe); + free(msg_unsafe); + fprintf(url, "%s: %s\n", log_level_array[level-1].log_string, msg_safe); + free(msg_safe); } else if (log_level == LOG_LEVEL_UNSET) { cache_line(level, format, ap); } diff --git before/src/report_json.c after/src/report_json.c index 2fc77bc..47e7fe7 100644 --- before/src/report_json.c +++ after/src/report_json.c @@ -29,6 +29,8 @@ #include "report.h" #include "seltree_struct.h" #include "stdbool.h" +#include "string.h" +#include "stdio.h" #include "url.h" #define JSON_FMT_ARRAY_BEGIN "%*c\"%s\": [\n" @@ -57,12 +59,53 @@ static int _escape_json_string(const char *src, char *escaped_string) { int n = 0; for (i = 0; i < strlen(src); ++i) { - if (src[i] == '\\') { - if (escaped_string) { escaped_string[n] = '\\'; } - n++; + switch(src[i]) { + case '\n': + if (escaped_string) { escaped_string[n] = '\\'; } + n++; + if (escaped_string) { escaped_string[n] = 'n'; } + n++; + break; + case '\t': + if (escaped_string) { escaped_string[n] = '\\'; } + n++; + if (escaped_string) { escaped_string[n] = 't'; } + n++; + break; + case '\b': + if (escaped_string) { escaped_string[n] = '\\'; } + n++; + if (escaped_string) { escaped_string[n] = 'b'; } + n++; + break; + case '\f': + if (escaped_string) { escaped_string[n] = '\\'; } + n++; + if (escaped_string) { escaped_string[n] = 'f'; } + n++; + break; + case '\r': + if (escaped_string) { escaped_string[n] = '\\'; } + n++; + if (escaped_string) { escaped_string[n] = 'r'; } + n++; + break; + case '"': + case '\\': + if (escaped_string) { escaped_string[n] = '\\'; } + n++; + if (escaped_string) { escaped_string[n] = src[i]; } + n++; + break; + default: + if (src[i] >= 0 && (src[i] < 0x1f || src[i] == 0x7f)) { + if (escaped_string) { snprintf(&escaped_string[n], 7, "\\u%04d", src[i]); } + n += 6; + } else { + if (escaped_string) { escaped_string[n] = src[i]; } + n++; + } } - if (escaped_string) { escaped_string[n] = src[i]; } - n++; } if (escaped_string) { escaped_string[n] = '\0'; } n++; @@ -302,9 +345,11 @@ static void print_report_diff_attrs_entries_json(report_t *report) { report_printf(report, JSON_FMT_OBJECT_BEGIN, 2, ' ', "different_attributes"); for(int i = 0; i < report->num_diff_attrs_entries; ++i) { char *str = NULL; + char *escaped_filename = _get_escaped_json_string(report->diff_attrs_entries[i].entry); report_printf(report, i+1num_diff_attrs_entries?JSON_FMT_STRING_COMMA:JSON_FMT_STRING_LAST , 4, ' ', - report->diff_attrs_entries[i].entry, + escaped_filename, str= diff_attributes(report->diff_attrs_entries[i].old_attrs, report->diff_attrs_entries[i].new_attrs)); + free(escaped_filename); free(str); } report->num_diff_attrs_entries = 0; diff --git before/src/report_plain.c after/src/report_plain.c index c87fc72..7d8a746 100644 --- before/src/report_plain.c +++ after/src/report_plain.c @@ -55,7 +55,9 @@ static char* _get_not_grouped_list_string(report_t *report) { static void _print_config_option(report_t *report, config_option option, const char* value) { if (first) { first=false; } else { report_printf(report," | "); } - report_printf(report, "%s: %s", config_options[option].report_string, value); + char *value_safe = stresc(value); + report_printf(report, "%s: %s", config_options[option].report_string, value_safe); + free(value_safe); } static void _print_report_option(report_t *report, config_option option, const char* value) { @@ -63,37 +65,49 @@ static void _print_report_option(report_t *report, config_option option, const c } static void _print_attribute(report_t *report, db_line* oline, db_line* nline, ATTRIBUTE attribute) { - char **ovalue = NULL; - char **nvalue = NULL; + char **ovalues = NULL; + char **nvalues = NULL; int onumber, nnumber, i, c; int p = (width_details-(4 + MAX_WIDTH_DETAILS_STRING))/2; DB_ATTR_TYPE attr = ATTR(attribute); const char* name = attributes[attribute].details_string; - onumber=get_attribute_values(attr, oline, &ovalue, report); - nnumber=get_attribute_values(attr, nline, &nvalue, report); + onumber=get_attribute_values(attr, oline, &ovalues, report); + nnumber=get_attribute_values(attr, nline, &nvalues, report); i = 0; while (i= 0 || nlen-p*k >= 0) { c = k*(p-1); if (!onumber) { - report_printf(report," %-*s%c %-*c %.*s\n", MAX_WIDTH_DETAILS_STRING, (i+k)?"":name, (i+k)?' ':':', p, ' ', p-1, nlen-c>0?&nvalue[i][c]:""); + report_printf(report," %-*s%c %-*c %.*s\n", MAX_WIDTH_DETAILS_STRING, (i+k)?"":name, (i+k)?' ':':', p, ' ', p-1, nlen-c>0?&nvalue[c]:""); } else if (!nnumber) { - report_printf(report," %-*s%c %.*s\n", MAX_WIDTH_DETAILS_STRING, (i+k)?"":name, (i+k)?' ':':', p-1, olen-c>0?&ovalue[i][c]:""); + report_printf(report," %-*s%c %.*s\n", MAX_WIDTH_DETAILS_STRING, (i+k)?"":name, (i+k)?' ':':', p-1, olen-c>0?&ovalue[c]:""); } else { - report_printf(report," %-*s%c %-*.*s| %.*s\n", MAX_WIDTH_DETAILS_STRING, (i+k)?"":name, (i+k)?' ':':', p, p-1, olen-c>0?&ovalue[i][c]:"", p-1, nlen-c>0?&nvalue[i][c]:""); + report_printf(report," %-*s%c %-*.*s| %.*s\n", MAX_WIDTH_DETAILS_STRING, (i+k)?"":name, (i+k)?' ':':', p, p-1, olen-c>0?&ovalue[c]:"", p-1, nlen-c>0?&nvalue[c]:""); } k++; } ++i; + free(ovalue); + free(nvalue); } - for(i=0; i < onumber; ++i) { free(ovalue[i]); ovalue[i]=NULL; } free(ovalue); ovalue=NULL; - for(i=0; i < nnumber; ++i) { free(nvalue[i]); nvalue[i]=NULL; } free(nvalue); nvalue=NULL; + for(i=0; i < onumber; ++i) { free(ovalues[i]); ovalues[i]=NULL; } free(ovalues); ovalues=NULL; + for(i=0; i < nnumber; ++i) { free(nvalues[i]); nvalues[i]=NULL; } free(nvalues); nvalues=NULL; } static void _print_database_attributes(report_t *report, db_line* db) { @@ -136,19 +150,21 @@ static void print_report_summary_plain(report_t *report) { } static void print_line_plain(report_t* report, seltree* node) { + char *filename_safe = stresc(((node->checked&NODE_REMOVED)?node->old_data:node->new_data)->filename); if(report->summarize_changes) { char* summary = get_summarize_changes_string(report, node); - report_printf(report, "\n%s: %s", summary, ((node->checked&NODE_REMOVED)?node->old_data:node->new_data)->filename); + report_printf(report, "\n%s: %s", summary, filename_safe); free(summary); summary=NULL; } else { if (node->checked&NODE_ADDED) { - report_printf(report, _("\nadded: %s"),(node->new_data)->filename); + report_printf(report, _("\nadded: %s"),filename_safe); } else if (node->checked&NODE_REMOVED) { - report_printf(report, _("\nremoved: %s"),(node->old_data)->filename); + report_printf(report, _("\nremoved: %s"),filename_safe); } else if (node->checked&NODE_CHANGED) { - report_printf(report, _("\nchanged: %s"),(node->new_data)->filename); + report_printf(report, _("\nchanged: %s"),filename_safe); } } + free(filename_safe); } static void print_report_dbline_attributes_plain(report_t *report, db_line* oline, db_line* nline, DB_ATTR_TYPE report_attrs) { @@ -158,7 +174,9 @@ static void print_report_dbline_attributes_plain(report_t *report, db_line* olin if (file_type) { report_printf(report, "%s: ", file_type); } - report_printf(report, "%s\n", (nline==NULL?oline:nline)->filename); + char *filename_safe = stresc((nline==NULL?oline:nline)->filename); + report_printf(report, "%s\n", filename_safe); + free(filename_safe); print_dbline_attrs(report, oline, nline, report_attrs, _print_attribute); } @@ -195,9 +213,11 @@ static void print_report_details_plain(report_t *report, seltree* node) { static void print_report_diff_attrs_entries_plain(report_t *report) { for(int i = 0; i < report->num_diff_attrs_entries; ++i) { char *str = NULL; + char *entry_safe = stresc(report->diff_attrs_entries[i].entry); report_printf(report, "Entry %s in databases has different attributes: %s\n", - report->diff_attrs_entries[i].entry, + entry_safe, str= diff_attributes(report->diff_attrs_entries[i].old_attrs, report->diff_attrs_entries[i].new_attrs)); + free(entry_safe); free(str); } report->num_diff_attrs_entries = 0; diff --git before/src/util.c after/src/util.c index 87f6801..f5c5e60 100644 --- before/src/util.c +++ after/src/util.c @@ -105,6 +105,40 @@ int cmpurl(url_t* u1,url_t* u2) return RETOK; } +static size_t escape_str(const char *unescaped_str, char *str, size_t s) { + size_t n = 0; + size_t i = 0; + char c; + while (i < s && (c = unescaped_str[i])) { + if ((c >= 0 && (c < 0x1f || c == 0x7f)) || + (c == '\\' && isdigit(unescaped_str[i+1]) + && isdigit(unescaped_str[i+2]) + && isdigit(unescaped_str[i+3]) + ) ) { + if (str) { snprintf(&str[n], 5, "\\%03o", c); } + n += 4; + } else { + if (str) { str[n] = c; } + n++; + } + i++; + } + if (str) { str[n] = '\0'; } + n++; + return n; +} + +char *strnesc(const char *unescaped_str, size_t s) { + int n = escape_str(unescaped_str, NULL, s); + char *str = checked_malloc(n); + escape_str(unescaped_str, str, s); + return str; +} + +char *stresc(const char *unescaped_str) { + return strnesc(unescaped_str, strlen(unescaped_str)); +} + /* Returns 1 if the string contains unsafe characters, 0 otherwise. */ int contains_unsafe (const char *s) {