diff --git a/bin/main b/bin/main index c385323..8d983d7 100755 Binary files a/bin/main and b/bin/main differ diff --git a/src/main.c b/src/main.c index 5e50a35..9b45b8b 100644 --- a/src/main.c +++ b/src/main.c @@ -32,6 +32,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND #define MAX_REQUEST_LENGTH 8192 // das hohe Limit ist erforderlich, da teilweise ausufernde JSON-Requests in nginx auflaufen können. #define TOP_X 20 // definiert die Anzahl der Einträge, die in den Top-Listen angezeigt werden sollen #define SUSPICIOUS_REQUEST_LEN_THRESHOLD 256 +#define WILDCARD_VALUE -1 // struct für die Darstellung von Timestamps. Die granulare Trennung in verschiedene int-Werte macht die spätere Verarbeitung modular anpassbar/erweiterbar und erleichtert die Verarbeitung. struct simple_time_t { @@ -81,6 +82,7 @@ struct time_filter_t { struct simple_time_t start_time; struct simple_time_t end_time; int filter_exclude_flag; + int is_cross_midnight; }; // Filter für User-Agent @@ -194,22 +196,84 @@ int month_name_to_number(char* month_name) { return 1; } -// Vergleich von Zeitstempel-Strukturen - time_checkvalue ist der Prüfwert, time_filtervalue ist der Vergleichswert (Filter) - Funktion wird in time_matches() aufgerufen +// Helper function to check if a range crosses midnight +int is_cross_midnight_range(struct simple_time_t start, struct simple_time_t end) { + // Only check for cross-midnight if we're dealing with time-only patterns + // (i.e., year, month, day are wildcards or same values) + + // If any date components are different and not wildcards, it's not cross-midnight + if (start.year != WILDCARD_VALUE && end.year != WILDCARD_VALUE && start.year != end.year) return 0; + if (start.month != WILDCARD_VALUE && end.month != WILDCARD_VALUE && start.month != end.month) return 0; + if (start.day != WILDCARD_VALUE && end.day != WILDCARD_VALUE && start.day != end.day) return 0; + + // Check if start time > end time (indicates cross-midnight) + if (start.hour != WILDCARD_VALUE && end.hour != WILDCARD_VALUE) { + if (start.hour > end.hour) return 1; + if (start.hour == end.hour) { + if (start.minute != WILDCARD_VALUE && end.minute != WILDCARD_VALUE) { + if (start.minute > end.minute) return 1; + if (start.minute == end.minute) { + if (start.second != WILDCARD_VALUE && end.second != WILDCARD_VALUE) { + if (start.second > end.second) return 1; + } + } + } + } + } + + return 0; +} + +// REPLACE the existing compare_times() function with this enhanced version: int compare_times(struct simple_time_t time_checkvalue, struct simple_time_t time_filtervalue) { /* - Rückgabewert 1 -> Prüfwert ist NACH dem Vergleichswert - Es wird erst das Jahr geprüft, dann der Monat u.s.w. - Ist ein Wert gleich, wird der nächstfeinere Wert geprüft. - Sobald die Werte sich unterscheiden, findet die Prüfung statt und ein Rückgabewert wird ausgegeben. - Gibt 0 zurück, wenn der Wert dem Filter exakt gleicht + Enhanced comparison that treats WILDCARD_VALUE (-1) as "match any" + Returns: -1 if checkvalue < filtervalue, 0 if equal/wildcard match, 1 if checkvalue > filtervalue */ - if (time_checkvalue.year != time_filtervalue.year) return (time_checkvalue.year < time_filtervalue.year) ? -1 : 1; - if (time_checkvalue.month != time_filtervalue.month) return (time_checkvalue.month < time_filtervalue.month) ? -1 : 1; - if (time_checkvalue.day != time_filtervalue.day) return (time_checkvalue.day < time_filtervalue.day) ? -1 : 1; - if (time_checkvalue.hour != time_filtervalue.hour) return (time_checkvalue.hour < time_filtervalue.hour) ? -1 : 1; - if (time_checkvalue.minute != time_filtervalue.minute) return (time_checkvalue.minute < time_filtervalue.minute) ? -1 : 1; - if (time_checkvalue.second != time_filtervalue.second) return (time_checkvalue.second < time_filtervalue.second) ? -1 : 1; - return 0; + + // Year comparison - skip if wildcard + if (time_filtervalue.year != WILDCARD_VALUE) { + if (time_checkvalue.year != time_filtervalue.year) { + return (time_checkvalue.year < time_filtervalue.year) ? -1 : 1; + } + } + + // Month comparison - skip if wildcard + if (time_filtervalue.month != WILDCARD_VALUE) { + if (time_checkvalue.month != time_filtervalue.month) { + return (time_checkvalue.month < time_filtervalue.month) ? -1 : 1; + } + } + + // Day comparison - skip if wildcard + if (time_filtervalue.day != WILDCARD_VALUE) { + if (time_checkvalue.day != time_filtervalue.day) { + return (time_checkvalue.day < time_filtervalue.day) ? -1 : 1; + } + } + + // Hour comparison - skip if wildcard + if (time_filtervalue.hour != WILDCARD_VALUE) { + if (time_checkvalue.hour != time_filtervalue.hour) { + return (time_checkvalue.hour < time_filtervalue.hour) ? -1 : 1; + } + } + + // Minute comparison - skip if wildcard + if (time_filtervalue.minute != WILDCARD_VALUE) { + if (time_checkvalue.minute != time_filtervalue.minute) { + return (time_checkvalue.minute < time_filtervalue.minute) ? -1 : 1; + } + } + + // Second comparison - skip if wildcard + if (time_filtervalue.second != WILDCARD_VALUE) { + if (time_checkvalue.second != time_filtervalue.second) { + return (time_checkvalue.second < time_filtervalue.second) ? -1 : 1; + } + } + + return 0; // All compared values match or are wildcards } // Standardfunktion zum Leeren des Input-Buffers @@ -219,20 +283,6 @@ void clear_input_buffer(){ } } -// Hilfsfunktion zum Prüfen, ob die Zahl eine Dezimalzahl ist. Wird im Menu verwendet, um die Nutzereingabe zu prüfen -int read_safe_integer(){ - int number; - int result = scanf("%d", &number); - - if (result != 1) { - clear_input_buffer(); - return -1; // Fehler, wenn die Nutzereingabe kein Integer ist - } - - clear_input_buffer(); - return number; -} - // Speicher freigeben und mit 0 überschreiben (Prävention von use-after-free-Schwachstelle) void cleanup_memory(){ if (all_entries != NULL) { @@ -595,6 +645,43 @@ void annotate_suspicious_entries(struct log_entry_t* dataset) { if (flag_verbose) printf("DEBUG: Analyse abgeschlossen. %d verdächtige Muster erkannt.\n", suspicious_patterns_count); } +// überall wo integer aus String-Input gelesen werden müssen. basiert auf strtol, was gegen Buffer Overflow sicher sein soll +int safe_read_integer(char* prompt, int min_val, int max_val) { + char input[50]; + int value; + char *endptr; + // endlos + while (1) { + printf("%s", prompt); + // scanf liest den Input in einen pointer ein, daher nicht &input. Die Usereingabe ist ein String, also ein Array + if (scanf("%49s", input) != 1) { + clear_input_buffer(); + printf("ERROR: Ungültige Eingabe. Bitte erneut versuchen.\n"); + continue; + } + clear_input_buffer(); + // Standard Rückgabewerte für die Menünavigation, die Werte werden für die choice-Variable genutzt + if (strcmp(input, "b") == 0 || strcmp(input, "B") == 0) return -2; + if (strcmp(input, "m") == 0 || strcmp(input, "M") == 0) return -3; + if (strcmp(input, "q") == 0 || strcmp(input, "Q") == 0) return -4; + + // Konvertierung der Eingabe in einen Long-Integer der Basis 10. Der endptr speichert einen Pointer auf das erste ungültige Zeichen nach einlesen des Lon-Integer + value = strtol(input, &endptr, 10); + // wenn der endptr der Nullterminator ist, handelte es sich bei der Eingabe sicher um einen Long-Integer. + if (*endptr != '\0') { + printf("ERROR: '%s' ist keine gültige Zahl. Bitte erneut versuchen.\n", input); + continue; + } + // Prüfen, ob sich der Wert im Erwartungsbereich befindet + if (value < min_val || value > max_val) { + printf("ERROR: Wert muss zwischen %d und %d liegen. Bitte erneut versuchen.\n", min_val, max_val); + continue; + } + + return value; + } +} + /* Parser. Regex Parser und strtok haben sich als schwieriger herausgestellt, da nginx Leerzeichen-getrennte Werte in den Logs hat, aber auch Leerzeichen innerhalb der Werte vorkommen. Daher ist das Parsing am einfachsten, wenn ein Pointer-basierter Algorithmus die Zeile Stück für Stück einliest und die Erwartungswerte in die struct schreibt. @@ -1142,36 +1229,84 @@ int annotation_matches(char* annotation) { return !has_include_filters; } -// Vergleicht einen übergebenen Prüfwert für einen Zeitstempel mit dem aktuell gesetzten Filter. Wenn der Prüfwert im Filterbereich ist,wird 1 zurückgegeben. +// Enhanced time comparison for cross-midnight patterns +int time_matches_pattern(struct simple_time_t entry_time, struct time_filter_t filter) { + if (filter.is_cross_midnight) { + // For cross-midnight ranges, entry matches if it's >= start OR <= end + int matches_after_start = 1; + int matches_before_end = 1; + + // Check if entry is >= start time (considering wildcards) + if (filter.start_time.hour != WILDCARD_VALUE) { + if (entry_time.hour < filter.start_time.hour) matches_after_start = 0; + else if (entry_time.hour == filter.start_time.hour) { + if (filter.start_time.minute != WILDCARD_VALUE) { + if (entry_time.minute < filter.start_time.minute) matches_after_start = 0; + else if (entry_time.minute == filter.start_time.minute) { + if (filter.start_time.second != WILDCARD_VALUE) { + if (entry_time.second < filter.start_time.second) matches_after_start = 0; + } + } + } + } + } + + // Check if entry is <= end time (considering wildcards) + if (filter.end_time.hour != WILDCARD_VALUE) { + if (entry_time.hour > filter.end_time.hour) matches_before_end = 0; + else if (entry_time.hour == filter.end_time.hour) { + if (filter.end_time.minute != WILDCARD_VALUE) { + if (entry_time.minute > filter.end_time.minute) matches_before_end = 0; + else if (entry_time.minute == filter.end_time.minute) { + if (filter.end_time.second != WILDCARD_VALUE) { + if (entry_time.second > filter.end_time.second) matches_before_end = 0; + } + } + } + } + } + + // For cross-midnight: match if EITHER condition is true + int time_matches = matches_after_start || matches_before_end; + + // Still need to check date components normally + int date_matches = 1; + if (filter.start_time.year != WILDCARD_VALUE && entry_time.year != filter.start_time.year) date_matches = 0; + if (filter.start_time.month != WILDCARD_VALUE && entry_time.month != filter.start_time.month) date_matches = 0; + if (filter.start_time.day != WILDCARD_VALUE && entry_time.day != filter.start_time.day) date_matches = 0; + + return time_matches && date_matches; + + } else { + // Normal range logic: entry must be >= start AND <= end + return (compare_times(entry_time, filter.start_time) >= 0 && + compare_times(entry_time, filter.end_time) <= 0); + } +} + int time_matches(struct simple_time_t entry_time) { - // kein Filter gesetzt - positiver Rückgabewert if (filters.time_count == 0) return 1; - // Übergeordneter Ausschlussfilter + // Check exclusion filters first (they have priority) for (int i = 0; i < filters.time_count; i++) { if (filters.time_filters[i].filter_exclude_flag == 1) { - int in_range = (compare_times(entry_time, filters.time_filters[i].start_time) >= 0 && - compare_times(entry_time, filters.time_filters[i].end_time) <= 0); - if (in_range) { - return 0; // zutreffender Ausschlussfilter führt zu negativem Rückgabewert, ist den Einschlussfiltern übergeordnet + if (time_matches_pattern(entry_time, filters.time_filters[i])) { + return 0; // Entry matches exclusion filter - exclude it } } } - // Einschlussfilter + // Check inclusion filters int has_include_filters = 0; for (int i = 0; i < filters.time_count; i++) { if (filters.time_filters[i].filter_exclude_flag == 0) { has_include_filters = 1; - int in_range = (compare_times(entry_time, filters.time_filters[i].start_time) >= 0 && - compare_times(entry_time, filters.time_filters[i].end_time) <= 0); - if (in_range) { - return 1; // Einschlussfilter passt + if (time_matches_pattern(entry_time, filters.time_filters[i])) { + return 1; // Entry matches inclusion filter } } } - // Wenn Einschlussfilter vorhanden sind, aber keiner passt return !has_include_filters; } @@ -1314,44 +1449,6 @@ void export_filtered_entries(char *filepath) { printf("INFO: %d Logeinträge erfolgreich als Timesketch-kompatible CSV-Datei nach '%s' exportiert.\n", exported_count, filename); } -// zeigt alle annotierten Einträge detailliert an -void show_annotated_entries() { - printf("\nLOGEINTRÄGE MIT ANNOTATION\n"); - printf("IP-Adresse | Methode | URL | Status | Bytes | User Agent | Zeit | Annotation\n"); - printf("-----------------|---------|------------------------|--------|-------|--------------------------------------|------------------|--------------------\n"); - - int shown_count = 0; - - for (int i = 0; i < total_entries; i++) { - if (!passes_filter(i)) continue; - - if (all_entries[i].annotated_flag == 1) { - printf("%-16s | %-7s | %-22s | %-6d | %-5d | %-36s | %02d.%02d.%d %02d:%02d:%02d | %-18s\n", - all_entries[i].ip_address, - all_entries[i].request_method, - all_entries[i].url_path, - all_entries[i].status_code, - all_entries[i].bytes_sent, - all_entries[i].user_agent, - all_entries[i].time.day, - all_entries[i].time.month, - all_entries[i].time.year, - all_entries[i].time.hour, - all_entries[i].time.minute, - all_entries[i].time.second, - all_entries[i].annotation - ); - shown_count++; - } - } - - if (shown_count == 0) { - printf("Keine annotierten Einträge gefunden, die dem Filter entsprechen.\n"); - } else { - printf("\nInsgesamt %d annotierte Einträge angezeigt.\n", shown_count); - } -} - // top-IP-Adressen sortieren nach Aufkommen und anzeigen void show_top_x_ips(){ // initialisieren von 1000 Datenstrukturen mit einem char ip_address[50] und int count (oben definiert) @@ -1485,36 +1582,35 @@ void show_top_user_agents(){ // Verwendung in einer Komplettausgabe sowie im Preview void show_filtered_entries(int num_shown) { int shown_count = 0; + char datetime[32]; - printf("\nLOGDATEN:\n"); - printf("IP-Adresse | Methode | URL | Status | Bytes | User Agent | Zeit | Annotation\n"); - printf("-----------------|---------|------------------------|--------|-------|--------------------------------------|---------------------|--------------------\n"); + printf("\n"); + printf("| TIMESTAMP | IP-ADRESSE | METHODE | URL/PFAD | STATUS | BYTES | REFERRER | USER-AGENT | ANNOTATION |\n"); + printf("|---------------------|-----------------|---------|------------------------|--------|-------|----------------------|----------------------|----------------------|\n"); for (int i = 0; i < total_entries; i++) { if (!passes_filter(i)) continue; - printf("%-16s | %-7s | %-22s | %-6d | %-5d | %-36s | %02d.%02d.%d %02d:%02d:%02d | %31s\n", + format_iso8601_time(all_entries[i].time, datetime, sizeof(datetime)); + + printf("| %-19.19s | %-15.15s | %-7.7s | %-32.32s | %-6d | %-5d | %-20.20s | %-30.30s | %-20.20s |\n", + datetime, all_entries[i].ip_address, all_entries[i].request_method, all_entries[i].url_path, all_entries[i].status_code, all_entries[i].bytes_sent, + all_entries[i].referrer, all_entries[i].user_agent, - all_entries[i].time.day, - all_entries[i].time.month, - all_entries[i].time.year, - all_entries[i].time.hour, - all_entries[i].time.minute, - all_entries[i].time.second, all_entries[i].annotation ); shown_count++; - if (num_shown != 0 && shown_count >= num_shown) break; // für Preview: abbrechen wenn num_shown erreicht + if (num_shown != 0 && shown_count >= num_shown) break; } if (num_shown != 0) { - printf("\nInsgesamt %d Einträge in der Vorschau.\n", shown_count); + printf("\n%d Zeilen von insgesamt %d Einträgen (gefiltert) angezeigt.\n", shown_count, count_filtered_entries()); } else { printf("\nInsgesamt %d Einträge gefunden.\n", shown_count); } @@ -1524,6 +1620,105 @@ void show_filtered_entries(int num_shown) { } } +// Helper function to format time component for display (handles wildcards) +void format_time_component(int value, char* buffer, int buffer_size) { + if (value == WILDCARD_VALUE) { + strncpy(buffer, "*", buffer_size - 1); + buffer[buffer_size - 1] = '\0'; + } else { + snprintf(buffer, buffer_size, "%02d", value); + } +} + +// Enhanced interactive time input with wildcard support +int safe_read_time_component_with_wildcard(char* prompt, int min_val, int max_val) { + char input[50]; + while (1) { + printf("%s (oder * für beliebig): ", prompt); + if (scanf("%49s", input) != 1) { + clear_input_buffer(); + printf("ERROR: Ungültige Eingabe. Bitte erneut versuchen.\n"); + continue; + } + clear_input_buffer(); + + // Handle navigation + if (strcmp(input, "b") == 0 || strcmp(input, "B") == 0) return -2; + if (strcmp(input, "m") == 0 || strcmp(input, "M") == 0) return -3; + if (strcmp(input, "q") == 0 || strcmp(input, "Q") == 0) return -4; + + // Handle wildcard + if (strcmp(input, "*") == 0) { + return WILDCARD_VALUE; + } + + // Parse number + char *endptr; + int value = strtol(input, &endptr, 10); + if (*endptr != '\0') { + printf("ERROR: '%s' ist keine gültige Zahl oder '*'. Bitte erneut versuchen.\n", input); + continue; + } + + if (value < min_val || value > max_val) { + printf("ERROR: Wert muss zwischen %d und %d liegen oder '*' sein. Bitte erneut versuchen.\n", min_val, max_val); + continue; + } + + return value; + } +} + +// Enhanced validation function +int validate_time_filter(struct time_filter_t *filter) { + char warnings[512] = ""; + int has_warnings = 0; + + // Check for potentially problematic wildcard combinations + if (filter->start_time.year == WILDCARD_VALUE && filter->end_time.year != WILDCARD_VALUE) { + strcat(warnings, "WARNING: Start year is wildcard but end year is specific. "); + has_warnings = 1; + } + + if (filter->start_time.month == WILDCARD_VALUE && filter->end_time.month != WILDCARD_VALUE) { + strcat(warnings, "WARNING: Start month is wildcard but end month is specific. "); + has_warnings = 1; + } + + if (filter->start_time.day == WILDCARD_VALUE && filter->end_time.day != WILDCARD_VALUE) { + strcat(warnings, "WARNING: Start day is wildcard but end day is specific. "); + has_warnings = 1; + } + + // Check for cross-midnight pattern and warn user + filter->is_cross_midnight = is_cross_midnight_range(filter->start_time, filter->end_time); + + if (filter->is_cross_midnight) { + printf("INFO: Cross-midnight pattern detected. Filter will match entries from start time until midnight AND from midnight until end time.\n"); + printf(" Example: 22:00-06:00 matches 22:00-23:59 AND 00:00-06:00\n"); + } + + // Validate logical consistency for non-wildcard components + if (!filter->is_cross_midnight) { + if (filter->start_time.year != WILDCARD_VALUE && filter->end_time.year != WILDCARD_VALUE && + filter->start_time.year > filter->end_time.year) { + printf("ERROR: Start year (%d) is after end year (%d)\n", filter->start_time.year, filter->end_time.year); + return 0; + } + + // Add similar checks for month, day if needed... + } + + if (has_warnings) { + printf("%s\n", warnings); + printf("Continue anyway? (1=Yes, 0=No): "); + int choice = safe_read_integer("", 0, 1); + if (choice != 1) return 0; + } + + return 1; +} + // Diese Funktion zeigt stets die gesetzten Filter in einem Format an, das aus dem interaktiven Modus reproduzierbar im cli-Modus wiederverwendet werden kann void print_filter_args(){ int total_filters = filters.status_count + filters.method_count + filters.ip_count + filters.time_count + filters.user_agent_count + filters.url_count + filters.annotation_flag_filter_enabled + filters.annotation_count; @@ -1584,26 +1779,45 @@ void print_filter_args(){ printf(" "); } - if (filters.time_count >0){ + if (filters.time_count > 0) { printf("--timerange="); - for(int i =0; i < filters.time_count;i++){ - if(i > 0) {printf(",");}; - if (filters.time_filters[i].filter_exclude_flag == 1) {printf("!");}; - // die führenden 0 sind hier wichtig - printf("%04d-%02d-%02d-%02d-%02d-%02d:%04d-%02d-%02d-%02d-%02d-%02d ", - filters.time_filters[i].start_time.year, - filters.time_filters[i].start_time.month, - filters.time_filters[i].start_time.day, - filters.time_filters[i].start_time.hour, - filters.time_filters[i].start_time.minute, - filters.time_filters[i].start_time.second, - filters.time_filters[i].end_time.year, - filters.time_filters[i].end_time.month, - filters.time_filters[i].end_time.day, - filters.time_filters[i].end_time.hour, - filters.time_filters[i].end_time.minute, - filters.time_filters[i].end_time.second); + for (int i = 0; i < filters.time_count; i++) { + if (i > 0) printf(","); + if (filters.time_filters[i].filter_exclude_flag == 1) printf("!"); + + // Format start time with wildcards + char year_str[5], month_str[3], day_str[3], hour_str[3], minute_str[3], second_str[3]; + + if (filters.time_filters[i].start_time.year == WILDCARD_VALUE) { + strcpy(year_str, "*"); + } else { + snprintf(year_str, sizeof(year_str), "%04d", filters.time_filters[i].start_time.year); + } + + format_time_component(filters.time_filters[i].start_time.month, month_str, sizeof(month_str)); + format_time_component(filters.time_filters[i].start_time.day, day_str, sizeof(day_str)); + format_time_component(filters.time_filters[i].start_time.hour, hour_str, sizeof(hour_str)); + format_time_component(filters.time_filters[i].start_time.minute, minute_str, sizeof(minute_str)); + format_time_component(filters.time_filters[i].start_time.second, second_str, sizeof(second_str)); + + printf("%s-%s-%s-%s-%s-%s:", year_str, month_str, day_str, hour_str, minute_str, second_str); + + // Format end time with wildcards + if (filters.time_filters[i].end_time.year == WILDCARD_VALUE) { + strcpy(year_str, "*"); + } else { + snprintf(year_str, sizeof(year_str), "%04d", filters.time_filters[i].end_time.year); + } + + format_time_component(filters.time_filters[i].end_time.month, month_str, sizeof(month_str)); + format_time_component(filters.time_filters[i].end_time.day, day_str, sizeof(day_str)); + format_time_component(filters.time_filters[i].end_time.hour, hour_str, sizeof(hour_str)); + format_time_component(filters.time_filters[i].end_time.minute, minute_str, sizeof(minute_str)); + format_time_component(filters.time_filters[i].end_time.second, second_str, sizeof(second_str)); + + printf("%s-%s-%s-%s-%s-%s", year_str, month_str, day_str, hour_str, minute_str, second_str); } + printf(" "); } // macht nicht viel Sinn, diese hier zu integrieren, so lang der User nicht weiß, wie die Annotationen lauten @@ -1765,6 +1979,36 @@ void print_filter_examples(){ printf(" Path Traversal - Directory-Traversal Angriffe\n"); printf(" Failed Auth - Fehlgeschlagene Authentifizierung\n"); printf(" Shell Access - Webshell-Zugriffe\n"); + + printf("\nWILDCARD-ZEITFILTER BEISPIELE (mit Cross-Midnight Support):\n\n"); + + printf("CROSS-MIDNIGHT BEREICHE:\n"); + printf("Nachtschicht (22:00-06:00):\n"); + printf(" --timerange=*-*-*-22-00-00:*-*-*-06-00-00\n"); + printf(" > Automatisch als Cross-Midnight erkannt\n"); + printf(" > Matched 22:00-23:59 UND 00:00-06:00\n\n"); + + printf("Wartungsfenster ausschließen (02:00-05:00):\n"); + printf(" --timerange=!*-*-*-02-00-00:*-*-*-05-00-00\n"); + printf(" > Schließt 02:00-05:00 jeden Tag aus\n\n"); + + printf("Wochenend-Abende (Freitag 18:00 bis Montag 08:00):\n"); + printf(" --timerange=*-*-*-18-00-00:*-*-*-08-00-00\n"); + printf(" > Cross-Midnight wird erkannt und korrekt behandelt\n\n"); + + printf("GESCHÄFTSZEITEN-MUSTER:\n"); + printf("Normale Arbeitszeit:\n"); + printf(" --timerange=*-*-*-08-00-00:*-*-*-18-00-00\n"); + printf(" > 8:00-18:00 jeden Tag\n\n"); + + printf("Außerhalb der Geschäftszeiten:\n"); + printf(" --timerange=*-*-*-18-00-01:*-*-*-07-59-59\n"); + printf(" > Cross-Midnight: 18:00-Mitternacht UND Mitternacht-08:00\n\n"); + + printf("MONATSSPEZIFISCHE MUSTER:\n"); + printf("Sommer-Monate (Juni-August):\n"); + printf(" --timerange=*-06-*-*-*-*:*-08-*-*-*-*\n"); + printf(" > Alle Sommer-Aktivitäten\n\n"); } int handle_menu_shortcuts(int choice) { @@ -1780,42 +2024,7 @@ int handle_menu_shortcuts(int choice) { return choice; } -// überall wo integer aus String-Input gelesen werden müssen. basiert auf strtol, was gegen Buffer Overflow sicher sein soll -int safe_read_integer(char* prompt, int min_val, int max_val) { - char input[50]; - int value; - char *endptr; - // endlos - while (1) { - printf("%s", prompt); - // scanf liest den Input in einen pointer ein, daher nicht &input. Die Usereingabe ist ein String, also ein Array - if (scanf("%49s", input) != 1) { - clear_input_buffer(); - printf("ERROR: Ungültige Eingabe. Bitte erneut versuchen.\n"); - continue; - } - clear_input_buffer(); - // Standard Rückgabewerte für die Menünavigation, die Werte werden für die choice-Variable genutzt - if (strcmp(input, "b") == 0 || strcmp(input, "B") == 0) return -2; - if (strcmp(input, "m") == 0 || strcmp(input, "M") == 0) return -3; - if (strcmp(input, "q") == 0 || strcmp(input, "Q") == 0) return -4; - - // Konvertierung der Eingabe in einen Long-Integer der Basis 10. Der endptr speichert einen Pointer auf das erste ungültige Zeichen nach einlesen des Lon-Integer - value = strtol(input, &endptr, 10); - // wenn der endptr der Nullterminator ist, handelte es sich bei der Eingabe sicher um einen Long-Integer. - if (*endptr != '\0') { - printf("ERROR: '%s' ist keine gültige Zahl. Bitte erneut versuchen.\n", input); - continue; - } - // Prüfen, ob sich der Wert im Erwartungsbereich befindet - if (value < min_val || value > max_val) { - printf("ERROR: Wert muss zwischen %d und %d liegen. Bitte erneut versuchen.\n", min_val, max_val); - continue; - } - - return value; - } -} + int safe_read_string(char* prompt, char* buffer, int buffer_size) { while (1) { @@ -1946,54 +2155,51 @@ int menu_set_filters(){ continue; } - printf("\nZEITRAUM FILTER HINZUFÜGEN\n"); + printf("\nZEITRAUM FILTER HINZUFÜGEN (Wildcards unterstützt)\n"); + printf("Verwenden Sie * für beliebige Werte (z.B. für Geschäftszeiten, Wochenenden)\n"); + printf("Cross-midnight Bereiche (z.B. 22:00-06:00) werden automatisch erkannt.\n\n"); + struct time_filter_t new_time_filter = {0}; printf("STARTZEIT:\n"); - int start_year = safe_read_integer("Jahr (z.B. 2025): ", 1970, 2100); - if (start_year < 0) continue; + int start_year = safe_read_time_component_with_wildcard("Jahr (z.B. 2025)", 1970, 2100); + if (start_year < -1) continue; - int start_month = safe_read_integer("Monat (1-12): ", 1, 12); - if (start_month < 0) continue; + int start_month = safe_read_time_component_with_wildcard("Monat (1-12)", 1, 12); + if (start_month < -1) continue; - int start_day = safe_read_integer("Tag (1-31): ", 1, 31); - if (start_day < 0) continue; + int start_day = safe_read_time_component_with_wildcard("Tag (1-31)", 1, 31); + if (start_day < -1) continue; - int start_hour = safe_read_integer("Stunde (0-23): ", 0, 23); - if (start_hour < 0) continue; + int start_hour = safe_read_time_component_with_wildcard("Stunde (0-23)", 0, 23); + if (start_hour < -1) continue; - int start_minute = safe_read_integer("Minute (0-59): ", 0, 59); - if (start_minute < 0) continue; + int start_minute = safe_read_time_component_with_wildcard("Minute (0-59)", 0, 59); + if (start_minute < -1) continue; - int start_second = safe_read_integer("Sekunde (0-59): ", 0, 59); - if (start_second < 0) continue; + int start_second = safe_read_time_component_with_wildcard("Sekunde (0-59)", 0, 59); + if (start_second < -1) continue; printf("\nENDZEIT:\n"); - int end_year = safe_read_integer("Jahr (z.B. 2025): ", 1970, 2100); - if (end_year < 0) continue; + int end_year = safe_read_time_component_with_wildcard("Jahr (z.B. 2025)", 1970, 2100); + if (end_year < -1) continue; - int end_month = safe_read_integer("Monat (1-12): ", 1, 12); - if (end_month < 0) continue; + int end_month = safe_read_time_component_with_wildcard("Monat (1-12)", 1, 12); + if (end_month < -1) continue; - int end_day = safe_read_integer("Tag (1-31): ", 1, 31); - if (end_day < 0) continue; + int end_day = safe_read_time_component_with_wildcard("Tag (1-31)", 1, 31); + if (end_day < -1) continue; - int end_hour = safe_read_integer("Stunde (0-23): ", 0, 23); - if (end_hour < 0) continue; + int end_hour = safe_read_time_component_with_wildcard("Stunde (0-23)", 0, 23); + if (end_hour < -1) continue; - int end_minute = safe_read_integer("Minute (0-59): ", 0, 59); - if (end_minute < 0) continue; + int end_minute = safe_read_time_component_with_wildcard("Minute (0-59)", 0, 59); + if (end_minute < -1) continue; - int end_second = safe_read_integer("Sekunde (0-59): ", 0, 59); - if (end_second < 0) continue; - - printf("\nFilter-Typ wählen:\n"); - printf("1. Einschließen (nur Ereignisse IN diesem Zeitraum)\n"); - printf("2. Ausschließen (Ereignisse in diesem Zeitraum NICHT anzeigen)\n"); - - int filter_type = safe_read_integer("Auswahl: ", 1, 2); - if (filter_type < 0) continue; + int end_second = safe_read_time_component_with_wildcard("Sekunde (0-59)", 0, 59); + if (end_second < -1) continue; + // Populate the filter structure new_time_filter.start_time.year = start_year; new_time_filter.start_time.month = start_month; new_time_filter.start_time.day = start_day; @@ -2008,12 +2214,27 @@ int menu_set_filters(){ new_time_filter.end_time.minute = end_minute; new_time_filter.end_time.second = end_second; + // Validate before asking for inclusion/exclusion + if (!validate_time_filter(&new_time_filter)) { + printf("Filter validation failed. Please try again.\n"); + continue; + } + + printf("\nFilter-Typ wählen:\n"); + printf("1. Einschließen (nur Ereignisse IN diesem Zeitraum/Muster)\n"); + printf("2. Ausschließen (Ereignisse in diesem Zeitraum/Muster NICHT anzeigen)\n"); + + int filter_type = safe_read_integer("Auswahl: ", 1, 2); + if (filter_type < 0) continue; + new_time_filter.filter_exclude_flag = (filter_type == 2) ? 1 : 0; + // Add to filters filters.time_filters[filters.time_count] = new_time_filter; filters.time_count++; - printf("Zeitraum-Filter hinzugefügt. Gesamt: %d\n", filters.time_count); + printf("Zeitraum-Filter mit Wildcards hinzugefügt. Gesamt: %d\n", filters.time_count); + } else if (choice == 4) { if (filters.user_agent_count >= MAX_FILTERS) { @@ -2169,21 +2390,50 @@ void menu_delete_filters(){ for (int i = 0; i < filters.time_count; i++) { char* mode_str = (filters.time_filters[i].filter_exclude_flag == 1) ? "ausschließen" : "einschließen"; - printf("%2d. Zeitraum: %02d.%02d.%d %02d:%02d:%02d - %02d.%02d.%d %02d:%02d:%02d (%s)\n", - filter_index++, - filters.time_filters[i].start_time.day, - filters.time_filters[i].start_time.month, - filters.time_filters[i].start_time.year, - filters.time_filters[i].start_time.hour, - filters.time_filters[i].start_time.minute, - filters.time_filters[i].start_time.second, - filters.time_filters[i].end_time.day, - filters.time_filters[i].end_time.month, - filters.time_filters[i].end_time.year, - filters.time_filters[i].end_time.hour, - filters.time_filters[i].end_time.minute, - filters.time_filters[i].end_time.second, - mode_str); + + // Format start time + char start_str[64]; + char temp[8]; + + char day_str[4], month_str[4], year_str[8], hour_str[4], minute_str[4], second_str[4]; + + format_time_component(filters.time_filters[i].start_time.day, day_str, sizeof(day_str)); + format_time_component(filters.time_filters[i].start_time.month, month_str, sizeof(month_str)); + if (filters.time_filters[i].start_time.year == WILDCARD_VALUE) { + strcpy(year_str, "*"); + } else { + snprintf(year_str, sizeof(year_str), "%04d", filters.time_filters[i].start_time.year); + } + format_time_component(filters.time_filters[i].start_time.hour, hour_str, sizeof(hour_str)); + format_time_component(filters.time_filters[i].start_time.minute, minute_str, sizeof(minute_str)); + format_time_component(filters.time_filters[i].start_time.second, second_str, sizeof(second_str)); + + snprintf(start_str, sizeof(start_str), "%s.%s.%s %s:%s:%s", + day_str, month_str, year_str, hour_str, minute_str, second_str); + + // Format end time + char end_str[64]; + format_time_component(filters.time_filters[i].end_time.day, day_str, sizeof(day_str)); + format_time_component(filters.time_filters[i].end_time.month, month_str, sizeof(month_str)); + if (filters.time_filters[i].end_time.year == WILDCARD_VALUE) { + strcpy(year_str, "*"); + } else { + snprintf(year_str, sizeof(year_str), "%04d", filters.time_filters[i].end_time.year); + } + format_time_component(filters.time_filters[i].end_time.hour, hour_str, sizeof(hour_str)); + format_time_component(filters.time_filters[i].end_time.minute, minute_str, sizeof(minute_str)); + format_time_component(filters.time_filters[i].end_time.second, second_str, sizeof(second_str)); + + snprintf(end_str, sizeof(end_str), "%s.%s.%s %s:%s:%s", + day_str, month_str, year_str, hour_str, minute_str, second_str); + + char cross_midnight_indicator[20] = ""; + if (filters.time_filters[i].is_cross_midnight) { + strcpy(cross_midnight_indicator, " [Cross-Midnight]"); + } + + printf("%2d. Zeitraum: %s - %s (%s)%s\n", + filter_index++, start_str, end_str, mode_str, cross_midnight_indicator); } if (filters.annotation_flag_filter_enabled){ @@ -2430,8 +2680,14 @@ void menu_show_entries(){ show_top_user_agents(); supress_preview = 1; } else if (choice == 5){ - show_annotated_entries(); + int temp_filterstate_exclude=filters.annotation_flag_filter.filter_exclude_flag; + int temp_filterstate_enabled=filters.annotation_flag_filter_enabled; + filters.annotation_flag_filter_enabled = 1; + filters.annotation_flag_filter.filter_exclude_flag = 0; supress_preview = 1; + show_filtered_entries(0); + filters.annotation_flag_filter_enabled = temp_filterstate_enabled; + filters.annotation_flag_filter.filter_exclude_flag = temp_filterstate_exclude; } else if (choice == -2) { return; } else if (choice == -3) { @@ -2549,148 +2805,134 @@ void add_parsed_url_filter(char* value, int filter_exclude_flag) { if (flag_verbose) printf("DEBUG: URL/Payload-Filter hinzugefügt: %s%s\n", filter_exclude_flag == 1 ? "!" : "", value); } -// Parsen des Timestamp-Filters ist etwas komplexer, da er in die Datenstruktur geschrieben werden muss. +// Helper function to parse a single time component (handles wildcards) +int parse_time_component(char* token, char* component_name, int min_val, int max_val) { + if (strcmp(token, "*") == 0) { + return WILDCARD_VALUE; + } + + int value = atoi(token); + if (value < min_val || value > max_val) { + printf("ERROR: %s außerhalb des möglichen Bereichs: %d (sollte %d-%d sein)\n", + component_name, value, min_val, max_val); + return -2; // Error value + } + + return value; +} + + + +// REPLACE the existing add_parsed_timerange_filter() function with this enhanced version: void add_parsed_timerange_filter(char* value, int filter_exclude_flag) { if (filters.time_count >= MAX_FILTERS) { printf("WARNING: MAX_FILTERS überschritten, ignoriere: %s\n", value); return; } - //lokale Kopie + + // Create local copy for parsing + char value_copy[256]; + strncpy(value_copy, value, sizeof(value_copy) - 1); + value_copy[sizeof(value_copy) - 1] = '\0'; + struct time_filter_t new_time_filter = {0}; - //Zur Position des :, der die Startzeit von der Endzeit trennt - char* colon_pos = strchr(value, ':'); + + // Find the colon separator + char* colon_pos = strchr(value_copy, ':'); if (colon_pos == NULL) { printf("ERROR: Missing ':' separator in timerange filter\n"); return; } - // ERsteze : durch Nullterminator. Das ist notwendig, damit strtok den Timestamp problemlos einlesen kann + + // Split at colon *colon_pos = '\0'; - //Für jeden Datenpunkt: Einlesen bis zum -, sowie Error handling, da atoi gar nix macht, wenn das Token nicht geparsed werden konnte - char* token = strtok(value, "-"); + char* start_part = value_copy; + char* end_part = colon_pos + 1; + + // Parse start time + char* token = strtok(start_part, "-"); if (!token) return; - int start_year = atoi(token); - if (start_year < 1970 || start_year > 2100) { - printf("ERROR: Startjahr außerhalb des möglichen Bereichs: %d\n", start_year); - return; - } + + new_time_filter.start_time.year = parse_time_component(token, "Startjahr", 1970, 2100); + if (new_time_filter.start_time.year == -2) return; token = strtok(NULL, "-"); if (!token) return; - int start_month = atoi(token); - if (start_month < 1 || start_month > 12) { - printf("ERROR: Startmonat außerhalb des möglichen Bereichs: %d\n", start_month); - return; - } + new_time_filter.start_time.month = parse_time_component(token, "Startmonat", 1, 12); + if (new_time_filter.start_time.month == -2) return; token = strtok(NULL, "-"); if (!token) return; - int start_day = atoi(token); - if (start_day < 1 || start_day > 31) { - printf("ERROR: Starttag außerhalb des möglichen Bereichs: %d\n", start_day); - return; - } + new_time_filter.start_time.day = parse_time_component(token, "Starttag", 1, 31); + if (new_time_filter.start_time.day == -2) return; token = strtok(NULL, "-"); if (!token) return; - int start_hour = atoi(token); - if (start_hour < 0 || start_hour > 23) { - printf("ERROR: Startstunde außerhalb des möglichen Bereichs: %d\n", start_hour); - return; - } + new_time_filter.start_time.hour = parse_time_component(token, "Startstunde", 0, 23); + if (new_time_filter.start_time.hour == -2) return; token = strtok(NULL, "-"); if (!token) return; - int start_minute = atoi(token); - if (start_minute < 0 || start_minute > 59) { - printf("ERROR: Startminute außerhalb des möglichen Bereichs: %d\n", start_minute); - return; - } + new_time_filter.start_time.minute = parse_time_component(token, "Startminute", 0, 59); + if (new_time_filter.start_time.minute == -2) return; token = strtok(NULL, "-"); if (!token) return; - int start_second = atoi(token); - if (start_second < 0 || start_second > 59) { - printf("ERROR: Startsekunde außerhalb des möglichen Bereichs: %d\n", start_second); - return; - } + new_time_filter.start_time.second = parse_time_component(token, "Startsekunde", 0, 59); + if (new_time_filter.start_time.second == -2) return; - token = strtok(colon_pos+1, "-"); + // Parse end time + token = strtok(end_part, "-"); if (!token) return; - int end_year = atoi(token); - if (end_year < 1970 || end_year > 2100) { - printf("ERROR: Endjahr außerhalb des möglichen Bereichs: %d\n", end_year); - return; - } + new_time_filter.end_time.year = parse_time_component(token, "Endjahr", 1970, 2100); + if (new_time_filter.end_time.year == -2) return; token = strtok(NULL, "-"); if (!token) return; - int end_month = atoi(token); - if (end_month < 1 || end_month > 12) { - printf("ERROR: Endmonat außerhalb des möglichen Bereichs: %d\n", end_month); - return; - } + new_time_filter.end_time.month = parse_time_component(token, "Endmonat", 1, 12); + if (new_time_filter.end_time.month == -2) return; token = strtok(NULL, "-"); if (!token) return; - int end_day = atoi(token); - if (end_day < 1 || end_day > 31) { - printf("ERROR: Endtag außerhalb des möglichen Bereichs: %d\n", end_day); - return; - } + new_time_filter.end_time.day = parse_time_component(token, "Endtag", 1, 31); + if (new_time_filter.end_time.day == -2) return; token = strtok(NULL, "-"); if (!token) return; - int end_hour = atoi(token); - if (end_hour < 0 || end_hour > 23) { - printf("ERROR: Endstunde außerhalb des möglichen Bereichs: %d\n", end_hour); - return; - } + new_time_filter.end_time.hour = parse_time_component(token, "Endstunde", 0, 23); + if (new_time_filter.end_time.hour == -2) return; token = strtok(NULL, "-"); if (!token) return; - int end_minute = atoi(token); - if (end_minute < 0 || end_minute > 59) { - printf("ERROR: Endminute außerhalb des möglichen Bereichs: %d\n", end_minute); - return; - } + new_time_filter.end_time.minute = parse_time_component(token, "Endminute", 0, 59); + if (new_time_filter.end_time.minute == -2) return; token = strtok(NULL, "-"); if (!token) return; - int end_second = atoi(token); - if (end_second < 0 || end_second > 59) { - printf("ERROR: Endsekunde außerhalb des möglichen Bereichs: %d\n", end_second); - return; - } - - // integer-Variablen in die lokale Datenstruktur schreiben - new_time_filter.start_time.year = start_year; - new_time_filter.start_time.month = start_month; - new_time_filter.start_time.day = start_day; - new_time_filter.start_time.hour = start_hour; - new_time_filter.start_time.minute = start_minute; - new_time_filter.start_time.second = start_second; - - new_time_filter.end_time.year = end_year; - new_time_filter.end_time.month = end_month; - new_time_filter.end_time.day = end_day; - new_time_filter.end_time.hour = end_hour; - new_time_filter.end_time.minute = end_minute; - new_time_filter.end_time.second = end_second; + new_time_filter.end_time.second = parse_time_component(token, "Endsekunde", 0, 59); + if (new_time_filter.end_time.second == -2) return; new_time_filter.filter_exclude_flag = filter_exclude_flag; - // Logik hier: der neue Filter wird stets an einen neuen Index geschrieben. Dieser ergibt sich aus der aktuellen ANZAHL der existierenden Zeitfilter. Das funktioniert, weil der index bei 0 beginnt + // Validate the filter before adding + if (!validate_time_filter(&new_time_filter)) { + printf("Filter validation failed. Not adding filter.\n"); + return; + } + + // Add to filters filters.time_filters[filters.time_count] = new_time_filter; filters.time_count++; - - if (flag_verbose) printf("DEBUG: Zeitraum-Filter hinzugefügt (%s): %04d-%02d-%02d %02d:%02d:%02d bis %04d-%02d-%02d %02d:%02d:%02d\n", - filter_exclude_flag == 1 ? "ausschließen" : "einschließen", - new_time_filter.start_time.year, new_time_filter.start_time.month, new_time_filter.start_time.day, - new_time_filter.start_time.hour, new_time_filter.start_time.minute, new_time_filter.start_time.second, - new_time_filter.end_time.year, new_time_filter.end_time.month, new_time_filter.end_time.day, - new_time_filter.end_time.hour, new_time_filter.end_time.minute, new_time_filter.end_time.second); + + if (flag_verbose) { + printf("DEBUG: Zeitraum-Filter hinzugefügt (%s)%s\n", + filter_exclude_flag == 1 ? "ausschließen" : "einschließen", + new_time_filter.is_cross_midnight ? " [Cross-Midnight]" : ""); + } } + + // recht einfache Mechanik für den Bool´schen Filter für annotierte Einträge void add_parsed_annotated_filter(char* value, int filter_exclude_flag) { if (strcmp(value, "true") == 0) { @@ -2825,13 +3067,15 @@ int parse_filter_argument(char* arg) { return 1; } -// DISCLAIMER: Updated and improved from original version +// DISCLAIMER: KI-generiert void print_help(char* binary) { - printf("\nNGINX EXAMINATOR - Improved Version\n"); + printf("\nNGINX EXAMINATOR\n"); printf("Verwendung:\n"); printf(" %s -i Interaktiver Modus (Filter/Analyse/Export)\n", binary); + printf(" %s -i -f [FILTER...] Interaktiv mit vorgemerkten Filtern\n", binary); printf(" %s -e Export generieren\n", binary); - printf(" %s -f [FILTER...] Mit Kommandozeilen-Filtern\n", binary); + printf(" %s -e -f [FILTER...] Export mit Filtern\n", binary); + printf(" %s -f [FILTER...] Nur Kommandozeilen-Filter\n", binary); printf(" %s -h Diese Hilfe anzeigen\n", binary); printf("\nArgumente:\n"); @@ -2842,7 +3086,7 @@ void print_help(char* binary) { printf(" -i Startet interaktiven Modus mit Filtern (Status, Methode, IP, Zeitraum,\n"); printf(" User-Agent, URL-Teilstring, Annotationen), Vorschau, Statistiken und CSV-Export (Timesketch).\n"); printf(" -e Generiert Timesketch-kompatible CSV-Datei, nimmt optional Dateinamen entgegen.\n"); - printf(" -f Aktiviert Kommandozeilen-Filter. Muss von Filter-Argumenten gefolgt werden.\n"); + printf(" -f Aktiviert Kommandozeilen-Filter. Kann mit -i oder -e kombiniert werden.\n"); printf(" -v Verbose-Modus für detaillierte Debug-Informationen.\n"); printf(" -h Zeigt diese Hilfe an.\n"); @@ -2856,23 +3100,64 @@ void print_help(char* binary) { printf(" --annotation=TEXT[,TEXT...] Annotations-Teilstrings (z.B. 'Long Payload')\n"); printf(" --timerange=START:END[,...] Zeiträume (Format: YYYY-MM-DD-HH-MM-SS:YYYY-MM-DD-HH-MM-SS)\n"); + printf("\nWICHTIG - SHELL-QUOTING:\n"); + printf(" Filter-Argumente mit Sonderzeichen müssen in Anführungszeichen stehen!\n"); + printf(" \n"); + printf(" RICHTIG:\n"); + printf(" \"--timerange=*-*-*-18-00-00:*-*-*-08-00-00\" (Wildcards * geschützt)\n"); + printf(" \"--useragent=!bot\" (Ausrufezeichen ! geschützt)\n"); + printf(" \"--url=*.php\" (Wildcards * geschützt)\n"); + printf(" \n"); + printf(" FALSCH:\n"); + printf(" --timerange=*-*-*-18-00-00:*-*-*-08-00-00 (Shell expandiert *)\n"); + printf(" --useragent=!bot (Shell-History-Problem)\n"); + printf(" \n"); + printf(" Grund: Shells interpretieren *, !, ? als spezielle Zeichen.\n"); + printf("\nFILTER-SYNTAX:\n"); printf(" Einschluss: Wert (nur Einträge MIT diesem Wert)\n"); printf(" Ausschluss: !Wert (alle Einträge OHNE diesen Wert)\n"); printf(" Mehrere: Wert1,Wert2 (kommagetrennt, keine Leerzeichen)\n"); printf(" Gemischt: Wert1,!Wert2 (Wert1 einschließen, Wert2 ausschließen)\n"); - printf("\nZEITRAUM-SYNTAX:\n"); + printf("\nZEITRAUM-SYNTAX (mit Wildcard-Unterstützung):\n"); printf(" Format: YYYY-MM-DD-HH-MM-SS:YYYY-MM-DD-HH-MM-SS\n"); - printf(" Beispiel: 2025-08-31-08-00-00:2025-08-31-18-00-00\n"); - printf(" Ausschluss: !2025-08-31-02-00-00:2025-08-31-06-00-00\n"); - printf(" Mehrere: Start1:End1,!Start2:End2,Start3:End3\n"); + printf(" Wildcards: Verwenden Sie * für beliebige Werte\n"); + printf(" Beispiele:\n"); + printf(" Geschäftszeiten: \"--timerange=*-*-*-08-00-00:*-*-*-18-00-00\"\n"); + printf(" Nachtzeit: \"--timerange=*-*-*-22-00-00:*-*-*-06-00-00\"\n"); + printf(" August 2025: \"--timerange=2025-08-*-*-*-*:2025-08-*-*-*-*\"\n"); + printf(" Wochenendzeit: \"--timerange=*-*-*-18-00-00:*-*-*-23-59-59\"\n"); + printf(" Ausschluss: \"--timerange=!*-*-*-02-00-00:*-*-*-06-00-00\" (Wartungszeit)\n"); + printf(" Cross-Midnight: Bereiche wie 22:00-06:00 werden automatisch erkannt\n"); - printf("\nFILTER-LOGIK (Vereinfacht):\n"); - printf(" - Ausschluss-Filter (!) haben IMMER Vorrang vor Einschluss-Filtern\n"); - printf(" - UND-Verknüpfung zwischen verschiedenen Filtertypen (IP UND Status UND Methode...)\n"); - printf(" - ODER-Verknüpfung innerhalb gleicher Filtertypen (Status 200 ODER 404)\n"); - printf(" - Einfache, vorhersagbare Logik ohne komplexe Modi\n"); + printf("\nMODUS-KOMBINATIONEN:\n"); + printf(" Interaktiv mit Filtern: %s /path/to/logs -i -f \"--status=200\" \"--method=GET\"\n", binary); + printf(" Export mit Filtern: %s /path/to/logs -e output -f \"--annotated=true\"\n", binary); + printf(" Nur Filter anzeigen: %s /path/to/logs -f \"--timerange=*-*-*-18-00-00:*-*-*-08-00-00\"\n", binary); + + printf("\nFILTER-LOGIK (Detailliert):\n"); + printf(" ZWISCHEN FILTERTYPEN (AND-Verknüpfung):\n"); + printf(" - Alle Filtertypen müssen bestanden werden (Status UND IP UND Methode UND Zeit...)\n"); + printf(" - Ein Eintrag muss JEDEN gesetzten Filtertyp bestehen, um angezeigt zu werden\n"); + printf(" - Beispiel: --status=200 --ip=192.168.1.1 → benötigt Status 200 UND IP 192.168.1.1\n\n"); + + printf(" INNERHALB EINES FILTERTYPS:\n"); + printf(" 1. AUSSCHLUSS-FILTER (!) haben absoluten Vorrang:\n"); + printf(" - Wenn IRGENDEIN !-Filter zutrifft → Eintrag wird ausgeschlossen\n"); + printf(" - Beispiel: --status=200,!404 und Eintrag hat Status 404 → ausgeschlossen\n\n"); + + printf(" 2. EINSCHLUSS-FILTER (ohne !):\n"); + printf(" - Wenn Einschluss-Filter existieren: MINDESTENS EINER muss zutreffen\n"); + printf(" - Wenn KEINE Einschluss-Filter existieren: alle Werte bestehen\n"); + printf(" - Beispiel: --status=200,404 → Status muss 200 ODER 404 sein\n\n"); + + printf(" PRAKTISCHE BEISPIELE:\n"); + printf(" \"--status=200,404\" → Status 200 oder 404\n"); + printf(" \"--status=!500\" → Alle außer Status 500\n"); + printf(" \"--status=200,!404\" → Status 200, aber nicht 404\n"); + printf(" \"--status=200\" \"--method=GET\" → Status 200 UND Methode GET\n"); + printf(" \"--ip=!192.168.1.0\" \"--status=!200\" → Nicht lokale IP UND nicht Status 200\n\n"); printf("\nUnterstützte Eingaben:\n"); printf(" - Normale NGINX-Access-Logs: *.log\n"); @@ -2896,14 +3181,6 @@ void print_help(char* binary) { printf(" - Filterbar über --annotated und --annotation Parameter\n"); printf(" - Sichtbar in interaktivem Modus und CSV-Export\n"); printf(" - Über 13 verschiedene Angriffsmuster werden erkannt\n"); - - printf("\nHinweise & Verbesserungen:\n"); - printf(" - Parser erwartet das obige Standardformat. Abweichungen können zum Abbruch führen.\n"); - printf(" - Verbesserte Speichersicherheit mit strncpy statt strcpy\n"); - printf(" - Vereinfachte, vorhersagbare Filterlogik ohne komplexe AND/OR-Modi\n"); - printf(" - Umfassende interaktive Benutzeroberfläche mit Navigation\n"); - printf(" - Automatische Anomalie-Erkennung für Sicherheitsanalysen\n"); - printf(" - ATYPICAL-Methode: für fehlerhafte/binäre Requests innerhalb der \"request\"-Spalte.\n"); } int main(int argc, char* argv[]) { @@ -2961,8 +3238,9 @@ int main(int argc, char* argv[]) { } else if (strcmp(argv[i], "-f") == 0) { flag_filter = 1; - // Parse subsequent filter arguments - for (int j = i + 1; j < argc; j++) { + // Parse subsequent filter arguments and skip them in main loop + int j; + for (j = i + 1; j < argc; j++) { if (starts_with(argv[j], "--")) { // Check for --input flag to override path if (starts_with(argv[j], "--input=")) { @@ -2978,6 +3256,8 @@ int main(int argc, char* argv[]) { break; // Stop parsing filters when we hit a non-filter argument } } + // Skip all the filter arguments we just processed in the main loop + i = j - 1; // -1 because the for loop will increment i } else if (starts_with(argv[i], "--input=")) { // Handle --input flag outside of -f context @@ -2989,6 +3269,7 @@ int main(int argc, char* argv[]) { } else if (starts_with(argv[i], "--")) { // Handle filter arguments that might be used without -f flag + // Only process these if we haven't seen -f flag yet parse_filter_argument(argv[i]); flag_filter = 1; // Implicit filter mode @@ -3008,7 +3289,7 @@ int main(int argc, char* argv[]) { } // Validate flag combinations - int active_modes = flag_interactive + flag_export + flag_filter; + int active_modes = flag_interactive + flag_export; if (active_modes > 1) { printf("ERROR: Nur ein Hauptmodus kann gleichzeitig verwendet werden (-i, -e, oder -f).\n"); printf("Verwenden Sie %s -h für Details.\n", argv[0]);