From 3d11d2f0f224abe04a75161c6a603cad923aef0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20St=C3=B6ckl?= Date: Mon, 1 Sep 2025 13:08:05 +0000 Subject: [PATCH] src/main.c aktualisiert --- src/main.c | 387 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 339 insertions(+), 48 deletions(-) diff --git a/src/main.c b/src/main.c index 9bd1075..9cb99c7 100644 --- a/src/main.c +++ b/src/main.c @@ -22,7 +22,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND #define INITIAL_ENTRIES 1000 // globale Variable zur initialen Speicherallokation in allocate_initial_memory(). Wird falls nötig um GROWTH_FACTOR erweitert #define GROWTH_FACTOR 2 // wird in mem_expand_dynamically() genutzt, um den Speicher zu vergrößern #define MAX_FILTERS 100 -#define MAX_URL_PATH_LENGTH 512 +#define MAX_REQUEST_LENGTH 1024 // das hohe Limit ist erforderlich, da teilweise ausufernde JSON-Requests in nginx auflaufen können. // definiert Variablen für den Filtermodus. FILTER_INCLUDE=0, FILTER_EXCLUDE=1. Verbessert die Lesbarkeit des Codes. typedef enum { @@ -44,10 +44,12 @@ struct simple_time { struct log_entry { char ip_address[50]; // ausreichende Längenbegrenzung für IP-Adressen. Könnte theoretisch auch ipv6 (ungetestet) char request_method[10]; // GET, POST, PUT, DELETE, PROPFIND ... - char url_path[MAX_URL_PATH_LENGTH]; // Pfade können lang werden, insbesodere bei base64-Strings wie oft in modernen Applikationen oder Malware verwendet + char url_path[MAX_REQUEST_LENGTH]; // Pfade können lang werden, insbesodere bei base64-Strings wie oft in modernen Applikationen oder Malware verwendet int status_code; int bytes_sent; struct simple_time time; + char referrer[128]; + char user_agent[256]; }; // Struktur für einen Status-Filtereintrag mit Inhalt & Modus @@ -69,6 +71,12 @@ struct time_filter { filter_mode_t mode; }; +// Filter für User-Agent +struct user_agent_filter { + char pattern[256]; + filter_mode_t mode; +}; + // Struktur zum erhalten aller Filtereinträge, kann im Dialogbetrieb bearbeitet werden. Mit Zähler. struct filter_system { struct status_filter status_filters[MAX_FILTERS]; @@ -79,6 +87,9 @@ struct filter_system { struct time_filter time_filters[MAX_FILTERS]; int time_count; + + struct user_agent_filter user_agent_filters[MAX_FILTERS]; + int user_agent_count; int combination_mode; // 0=AND-Filter oder 1=OR-Filter }; @@ -198,7 +209,7 @@ void mem_expand_dynamically() { struct log_entry *new_ptr = realloc(all_entries, max_entries * sizeof(struct log_entry)); if (new_ptr == NULL) { - printf("ERROR: Speicher konnte nicht auf %d Einträge erweitert werden, Programm wird beendet...\n", max_entries); + printf("ERROR: Speicher konnte nicht auf %d Einträge erweitert werden, ..\n", max_entries); printf("ERROR: Benötigter Speicher: %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry))); cleanup_and_exit(); } @@ -213,7 +224,7 @@ void allocate_initial_memory() { all_entries = malloc(max_entries * sizeof(struct log_entry)); if (all_entries == NULL) { - printf("ERROR: Konnte %d Einträge nicht allozieren, Programm wird beendet...\n", max_entries); + printf("ERROR: Konnte %d Einträge nicht allozieren, ..\n", max_entries); printf("ERROR: %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry))); exit(1); // cleanup_and_exit() nicht nötig, da der Speicherbereich nicht beschrieben wurde - use-after-free unproblematisch } @@ -258,8 +269,10 @@ Daher ist das Parsing am einfachsten, wenn ein Pointer-basierter Algorithmus die Fehleranfällig, wenn das Logformat nicht dem Standard entspricht - das gilt aber auch für andere Parser. */ // Standard-nginx-accesslog: +// Standard-nginx-accesslog: // 107.170.27.248 - - [31/Aug/2025:00:11:42 +0000] "GET /.git/config HTTP/1.1" 400 255 "-" "Mozilla/5.0; Keydrop.io/1.0(onlyscans.com/about);" "-" int parse_simple_log_line(char* line, int entry_index) { // Nimmt den Pointer auf die Zeile und einen Index entgegen - dieser ist anfangs 0 (globale Variable) und wird pro Eintrag inkrementiert + printf("Parsing line: %s\n", line); char* current_pos = line; // leere Zeichen am Anfang überspringen current_pos = skip_spaces(current_pos); @@ -280,6 +293,8 @@ int parse_simple_log_line(char* line, int entry_index) { // Nimmt den Pointer au // mehrmaliges Überspringen des Wertes, der nicht von Interesse ist, Überspringen des Leerzeichens while (*current_pos != ' ' && *current_pos != '\0') current_pos++; current_pos = skip_spaces(current_pos); + // 107.170.27.248 - - [31/Aug/2025:00:11:42 +0000] "GET /.git/config HTTP/1.1" 400 255 "-" "Mozilla/5.0; Keydrop.io/1.0(onlyscans.com/about);" "-" + // ^ while (*current_pos != ' ' && *current_pos != '\0') current_pos++; current_pos = skip_spaces(current_pos); // 107.170.27.248 - - [31/Aug/2025:00:11:42 +0000] "GET /.git/config HTTP/1.1" 400 255 "-" "Mozilla/5.0; Keydrop.io/1.0(onlyscans.com/about);" "-" @@ -344,35 +359,85 @@ int parse_simple_log_line(char* line, int entry_index) { // Nimmt den Pointer au // 107.170.27.248 - - [31/Aug/2025:00:11:42 +0000] "GET /.git/config HTTP/1.1" 400 255 "-" "Mozilla/5.0; Keydrop.io/1.0(onlyscans.com/about);" "-" // ^ } else { - printf("ERROR: Unerwartetes Log-Format. Lediglich mit standard-nginx-accesslog kompatibel.\nDer Fehler ist beim Prüfen des Timestamps aufgetreten, dieser sollte folgendes Format haben:\n[DD/MMM/YYYY:HH:MM:SS +0000]\nProgramm wird beendet.\n"); + printf("ERROR: Unerwartetes Log-Format. Lediglich mit standard-nginx-accesslog kompatibel.\nDer Fehler ist beim Prüfen des Timestamps aufgetreten, dieser sollte folgendes Format haben:\n[DD/MMM/YYYY:HH:MM:SS +0000]\n\n"); cleanup_and_exit(); } current_pos = skip_spaces(current_pos); // Weiter mit dem String innerhalb "", aus dem die HTTP-Methode und der URL-Pfad zu entnehmen ist + // Enhanced parsing to handle malformed binary requests gracefully if (*current_pos == '"') { current_pos++; // 107.170.27.248 - - [31/Aug/2025:00:11:42 +0000] "GET /.git/config HTTP/1.1" 400 255 "-" "Mozilla/5.0; Keydrop.io/1.0(onlyscans.com/about);" "-" // ^ - // HTTP-Methode bis zum nächsten Leerzeichen einlesen und speichern - copy_until_space(all_entries[entry_index].request_method, current_pos, sizeof(all_entries[entry_index].request_method)); - while (*current_pos != ' ' && *current_pos != '\0') current_pos++; - current_pos = skip_spaces(current_pos); - // 107.170.27.248 - - [31/Aug/2025:00:11:42 +0000] "GET /.git/config HTTP/1.1" 400 255 "-" "Mozilla/5.0; Keydrop.io/1.0(onlyscans.com/about);" "-" - // ^ - // Einlesen des URL-Path bis zum abschließenden " - int i = 0; - while (*current_pos != ' ' && *current_pos != '"' && *current_pos != '\0' && i < sizeof(all_entries[entry_index].url_path) - 1) { - all_entries[entry_index].url_path[i] = *current_pos; - i++; - current_pos++; - } - all_entries[entry_index].url_path[i] = '\0'; - while (*current_pos != '"' && *current_pos != '\0') current_pos++; - if (*current_pos == '"') current_pos++; + // First, try to read what should be the HTTP method + char temp_method[50]; + copy_until_space(temp_method, current_pos, sizeof(temp_method)); + + // Check if it looks like a valid HTTP method (starts with letters, reasonable length) + int is_valid_method = 1; + if (strlen(temp_method) == 0 || strlen(temp_method) > 10) { + is_valid_method = 0; + } else { + // Check if method contains only letters (no binary data) + for (int i = 0; temp_method[i] != '\0'; i++) { + if (!((temp_method[i] >= 'A' && temp_method[i] <= 'Z') || + (temp_method[i] >= 'a' && temp_method[i] <= 'z'))) { + is_valid_method = 0; + break; + } + } + } + + if (is_valid_method) { + // Normal parsing: HTTP-Methode bis zum nächsten Leerzeichen einlesen und speichern + strcpy(all_entries[entry_index].request_method, temp_method); + while (*current_pos != ' ' && *current_pos != '\0') current_pos++; + current_pos = skip_spaces(current_pos); + // 107.170.27.248 - - [31/Aug/2025:00:11:42 +0000] "GET /.git/config HTTP/1.1" 400 255 "-" "Mozilla/5.0; Keydrop.io/1.0(onlyscans.com/about);" "-" + // ^ + // Einlesen des URL-Path bis zum abschließenden " + int i = 0; + while (*current_pos != ' ' && *current_pos != '"' && *current_pos != '\0' && i < sizeof(all_entries[entry_index].url_path) - 1) { + all_entries[entry_index].url_path[i] = *current_pos; + i++; + current_pos++; + } + all_entries[entry_index].url_path[i] = '\0'; + + while (*current_pos != '"' && *current_pos != '\0') current_pos++; + if (*current_pos == '"') { + current_pos++; + } + } else { + // in NGINX treten gelegentlich fehlerhafte Requests auf, die binäre Daten übersenden, sodass normales parsen nicht möglich ist. + // der entsprechende Eintrag wird daher mit dem String "MALFORMED" repräsentiert + strcpy(all_entries[entry_index].request_method, "MALFORMED"); + + // Read entire quoted content into url_path for forensic analysis + int i = 0; + while (*current_pos != '"' && *current_pos != '\0' && + i < sizeof(all_entries[entry_index].url_path) - 1) { + all_entries[entry_index].url_path[i] = *current_pos; + i++; + current_pos++; + } + all_entries[entry_index].url_path[i] = '\0'; + + // zum Ende des request-strings vorarbeiten, wenn der String zu lang war. + while (*current_pos != '"' && *current_pos != '\0') { + current_pos++; + } + + if (*current_pos == '"') current_pos++; + + printf("INFO: Fehlerhaften Logeintrag entdeckt, speichere mit MALFORMED-Eintrag.\n"); + } + // 107.170.27.248 - - [31/Aug/2025:00:11:42 +0000] "GET /.git/config HTTP/1.1" 400 255 "-" "Mozilla/5.0; Keydrop.io/1.0(onlyscans.com/about);" "-" + // ^ } else { - printf("ERROR: Unerwartetes Log-Format. Lediglich mit standard-nginx-accesslog kompatibel.\nDer Fehler ist beim Prüfen der HTTP-Methode aufgetreten. Diese steht innerhalb eines Strings zusammen mit dem URL-Pfad:\n\"GET /.git/config HTTP/1.1\"\nProgramm wird beendet.\n"); + printf("ERROR: Unerwartetes Log-Format. Lediglich mit standard-nginx-accesslog kompatibel.\nDer Fehler ist beim Prüfen der HTTP-Methode aufgetreten. Diese steht innerhalb eines Strings zusammen mit dem URL-Pfad:\n\"GET /.git/config HTTP/1.1\"\n\n"); cleanup_and_exit(); } @@ -388,12 +453,44 @@ int parse_simple_log_line(char* line, int entry_index) { // Nimmt den Pointer au current_pos = skip_spaces(current_pos); // genauso mit bytegröße der Anfrage + // 107.170.27.248 - - [31/Aug/2025:00:11:42 +0000] "GET /.git/config HTTP/1.1" 400 255 "-" "Mozilla/5.0; Keydrop.io/1.0(onlyscans.com/about);" "-" + // ^ all_entries[entry_index].bytes_sent = 0; while (*current_pos >= '0' && *current_pos <= '9') { all_entries[entry_index].bytes_sent = all_entries[entry_index].bytes_sent * 10 + (*current_pos - '0'); current_pos++; } - // TODO: Der User Agent wäre noch eine interessante Metrik. Kann relativ einfach implementiert werden. + current_pos = skip_spaces(current_pos); + + // Parsen des Referrer-Feldes innerhalb "", wird übersprungen da nicht gespeichert + if (*current_pos == '"') { + current_pos++; // öffnendes Anführungszeichen überspringen + // Referrer-Inhalt bis zum schließenden Anführungszeichen überspringen + while (*current_pos != '"' && *current_pos != '\0') { + current_pos++; + } + if (*current_pos == '"') current_pos++; // schließendes Anführungszeichen überspringen + } else { + printf("ERROR: Unerwartetes Log-Format. Lediglich mit standard-nginx-accesslog kompatibel.\nDer Fehler ist beim Prüfen des Referrer-Feldes aufgetreten.\n\n"); + cleanup_and_exit(); + } + + current_pos = skip_spaces(current_pos); + // parsen des user agents innerhalb "" + if (*current_pos == '"') { + current_pos++; + int i = 0; + while (*current_pos != '"' && *current_pos != '\0' && i < sizeof(all_entries[entry_index].user_agent) - 1) { + all_entries[entry_index].user_agent[i] = *current_pos; + i++; + current_pos++; + } + all_entries[entry_index].user_agent[i] = '\0'; + if (*current_pos == '"') current_pos++; + } else { + printf("ERROR: Unerwartetes Log-Format. Lediglich mit standard-nginx-accesslog kompatibel.\nDer Fehler ist beim Prüfen des User-Agent aufgetreten. Dieser steht innerhalb eines Strings:\n\"Mozilla/5.0; Keydrop.io/1.0(onlyscans.com/about);\"\n\n"); + cleanup_and_exit(); + } return 1; } @@ -484,6 +581,76 @@ void load_log_file(char* path) { printf("Aktueller Speicherverbrauch: %lu Bytes für %d Einträge\n", (unsigned long)(max_entries * sizeof(struct log_entry)), max_entries); } +// Funktion zum suchen eines Suchbegriffs innerhalb eines Strings (lowercase) +int search_in_string(const char* raw_string, const char* search_string) { + char raw_string_lower[512]; // Puffer zum Speichern des zu durchsuchenden Strings + char search_string_lower[256]; // Puffer zum Speichern des Suchbegriffs + + // Konvertierung des Datensatzes zu Kleinbuchstaben + int i = 0; + // für jeden Buchstaben innerhalb des Datensatzes Verschiebung innerhalb des ASCII-Alphabets um 32 + while (raw_string[i] && i < 511) { + if (raw_string[i] >= 'A' && raw_string[i] <= 'Z') { + raw_string_lower[i] = raw_string[i] + 32; // Verschiebung im ASCII-Alphabet + } else { + raw_string_lower[i] = raw_string[i]; // alles was kein Buchstabe ist, wird beibehalten + } + i++; + } + raw_string_lower[i] = '\0'; // Nullterminator anfügen + + // gleiche Methode mit dem Suchbegriff + i = 0; + while (search_string[i] && i < 255) { + if (search_string[i] >= 'A' && search_string[i] <= 'Z') { + search_string_lower[i] = search_string[i] + 32; // Verschiebung im ASCII-Alphabet + } else { + search_string_lower[i] = search_string[i]; // nicht-Buchstaben beibehalten + } + i++; + } + search_string_lower[i] = '\0'; // Nullterminator anfügen + + // strstr()-Vergleich - gibt NULL zurück wenn nichts gefunden + char* result = strstr(raw_string_lower, search_string_lower); + + // Einfache Rückgabe: 1 wenn gefunden, 0 wenn nicht gefunden + if (result != NULL) { + return 1; + } else { + return 0; + } +} + +// Filterfunktion für den User-Agent. Nimmt den Datensatz entgegen und prüft gegen die gesetzten Filter, gibt dann 0 oder 1 zurück +int user_agent_matches(char* user_agent) { + if (filters.user_agent_count == 0) return 1; + + int has_include_filters = 0; + int include_match = 0; + + for (int i = 0; i < filters.user_agent_count; i++) { + int pattern_found = search_in_string(user_agent, filters.user_agent_filters[i].pattern); + + if (filters.user_agent_filters[i].mode == FILTER_INCLUDE) { + has_include_filters = 1; + if (pattern_found) { + include_match = 1; + } + } else { // ausschließen-Filter + if (pattern_found) { + return 0; + } + } + } + + if (has_include_filters) { + return include_match; + } + + return 1; +} + int status_code_matches(int status_code) { if (filters.status_count == 0) return 1; @@ -573,14 +740,14 @@ int passes_filter(int entry_index) { int status_match = status_code_matches(all_entries[entry_index].status_code); int ip_match = ip_address_matches(all_entries[entry_index].ip_address); int time_match = time_matches(all_entries[entry_index].time); - + int user_agent_match = user_agent_matches(all_entries[entry_index].user_agent); if (filters.combination_mode == 0) { - return status_match && ip_match && time_match; + return status_match && ip_match && time_match && user_agent_match; } else { - int total_filters = filters.status_count + filters.ip_count + filters.time_count; + int total_filters = filters.status_count + filters.ip_count + filters.time_count + filters.user_agent_count; if (total_filters == 0) return 1; - return status_match || ip_match || time_match; + return status_match || ip_match || time_match || user_agent_match; } } @@ -606,12 +773,13 @@ void show_status() { } printf("\n🔍 Aktive Filter:\n"); - int total_filters = filters.status_count + filters.ip_count + filters.time_count; + int total_filters = filters.status_count + filters.ip_count + filters.time_count + filters.user_agent_count; // UPDATE THIS LINE if (total_filters == 0) { printf(" Keine Filter gesetzt\n"); + printf(" Filter-Modus: %s\n", filters.combination_mode == 0 ? "AND" : "OR"); } else { - printf(" Filter-Modus: %s\n", filters.combination_mode == 0 ? "AND (alle müssen zutreffen)" : "OR (einer muss zutreffen)"); + printf(" Filter-Modus: %s\n", filters.combination_mode == 0 ? "AND" : "OR"); if (filters.status_count > 0) { printf(" Status-Codes (%d): ", filters.status_count); @@ -633,6 +801,16 @@ void show_status() { printf("\n"); } + if (filters.user_agent_count > 0) { + printf(" User-Agent Pattern (%d): ", filters.user_agent_count); + for (int i = 0; i < filters.user_agent_count; i++) { + char mode_char = (filters.user_agent_filters[i].mode == FILTER_EXCLUDE) ? '!' : ' '; + printf("%c\"%s\"", mode_char, filters.user_agent_filters[i].pattern); + if (i < filters.user_agent_count - 1) printf(", "); + } + printf("\n"); + } + if (filters.time_count > 0) { printf(" Zeiträume (%d):\n", filters.time_count); for (int i = 0; i < filters.time_count; i++) { @@ -704,11 +882,11 @@ void export_filtered_entries() { FILE* file = fopen(filename, "w"); if (file == NULL) { - printf("FEHLER: Kann Datei '%s' nicht erstellen!\n", filename); + printf("ERROR: Kann Datei '%s' nicht erstellen!\n", filename); return; } - fprintf(file, "message,datetime,timestamp_desc,timestamp,ip_address,method,url_path,status_code,bytes_sent\n"); + fprintf(file, "message,datetime,timestamp_desc,timestamp,ip_address,method,url_path,status_code,bytes_sent,user_agent\n"); int exported_count = 0; char iso_datetime[32]; @@ -726,7 +904,8 @@ void export_filtered_entries() { all_entries[i].request_method, all_entries[i].url_path, all_entries[i].status_code, - all_entries[i].bytes_sent); + all_entries[i].bytes_sent, + all_entries[i].user_agent); fprintf(file, "\"%s\",\"%s\",\"HTTP Access Log\",%lld,\"%s\",\"%s\",\"%s\",%d,%d\n", message_text, @@ -736,14 +915,15 @@ void export_filtered_entries() { all_entries[i].request_method, all_entries[i].url_path, all_entries[i].status_code, - all_entries[i].bytes_sent); + all_entries[i].bytes_sent, + all_entries[i].user_agent); exported_count++; } } fclose(file); - printf("✅ %d Einträge erfolgreich als Timesketch-kompatible CSV nach '%s' exportiert.\n", exported_count, filename); + printf("%d Logeinträge erfolgreich als Timesketch-kompatible CSV-Datei nach '%s' exportiert.\n", exported_count, filename); } struct ip_stat { @@ -805,12 +985,72 @@ void show_top_10_ips() { } } +void show_top_user_agents() { + struct user_agent_stat { + char user_agent[256]; + int count; + }; + + struct user_agent_stat agent_stats[1000]; + int unique_agents = 0; + + for (int i = 0; i < total_entries; i++) { + if (!passes_filter(i)) continue; + + char* current_agent = all_entries[i].user_agent; + int found_index = -1; + + for (int j = 0; j < unique_agents; j++) { + if (strcmp(agent_stats[j].user_agent, current_agent) == 0) { + found_index = j; + break; + } + } + + if (found_index >= 0) { + agent_stats[found_index].count++; + } else { + if (unique_agents < 1000) { + strcpy(agent_stats[unique_agents].user_agent, current_agent); + agent_stats[unique_agents].count = 1; + unique_agents++; + } + } + } + + // Sort by count (descending) + for (int i = 0; i < unique_agents - 1; i++) { + for (int j = 0; j < unique_agents - i - 1; j++) { + if (agent_stats[j].count < agent_stats[j + 1].count) { + struct user_agent_stat temp = agent_stats[j]; + agent_stats[j] = agent_stats[j + 1]; + agent_stats[j + 1] = temp; + } + } + } + + printf("\n=== TOP 10 USER AGENTS ===\n"); + printf("Rang | User Agent | Anzahl Anfragen\n"); + printf("-----|--------------------------------------|----------------\n"); + + int show_count = (unique_agents < 10) ? unique_agents : 10; + for (int i = 0; i < show_count; i++) { + printf("%-4d | %-34s | %d\n", i + 1, agent_stats[i].user_agent, agent_stats[i].count); + } + + if (unique_agents == 0) { + printf("Keine User Agents in den gefilterten Daten gefunden.\n"); + } else { + printf("\nInsgesamt %d verschiedene User Agents gefunden.\n", unique_agents); + } +} + void show_filtered_entries() { int shown_count = 0; printf("\n=== GEFILTERTE LOG-EINTRÄGE ===\n"); - printf("IP-Adresse | Methode | URL | Status | Bytes | Zeit\n"); - printf("-----------------|---------|------------------------|--------|-------|------------------\n"); + printf("IP-Adresse | Methode | URL | Status | Bytes | User Agent | Zeit\n"); + printf("-----------------|---------|------------------------|--------|-------|--------------------------------------|------------------\n"); for (int i = 0; i < total_entries; i++) { if (passes_filter(i)) { @@ -820,6 +1060,7 @@ void show_filtered_entries() { 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, @@ -874,10 +1115,11 @@ void menu_set_filters() { while (choice != 4) { show_status(); printf("\n=== FILTER SETZEN ===\n"); - printf("1. Status-Code Filter hinzufügen\n"); - printf("2. IP-Adresse Filter hinzufügen\n"); - printf("3. Zeitraum Filter hinzufügen\n"); - printf("4. Zurück zum Filter-Menü\n"); + printf("1. Status-Code Filter hinzufügen (exakte Suche)\n"); + printf("2. IP-Adresse Filter hinzufügen (exakte Suche)\n"); + printf("3. Zeitraum Filter hinzufügen (interaktiv)\n"); + printf("4. User-Agent-Filter setzen (Freitext)\n"); + printf("5. Zurück zum Filter-Menü\n"); printf("Auswahl: "); choice = read_safe_integer(); @@ -1047,17 +1289,45 @@ void menu_set_filters() { filters.time_count++; printf("✅ Zeitraum-Filter hinzugefügt. Total: %d\n", filters.time_count); - + } else if (choice == 4) { + if (filters.user_agent_count >= MAX_FILTERS) { + printf("FEHLER: Maximale Anzahl User-Agent Filter erreicht (%d)!\n", MAX_FILTERS); + continue; + } + + printf("User-Agent Suchtext eingeben (z.B. 'chrome', 'bot', 'upti'): "); + char pattern[256]; + if (scanf("%255s", pattern) == 1) { + printf("Filter-Typ wählen:\n"); + printf("1. Einschließen (nur User-Agents mit '%s' anzeigen)\n", pattern); + printf("2. Ausschließen (User-Agents mit '%s' NICHT anzeigen)\n", pattern); + printf("Auswahl: "); + int filter_type = read_safe_integer(); + + if (filter_type == 1 || filter_type == 2) { + strcpy(filters.user_agent_filters[filters.user_agent_count].pattern, pattern); + filters.user_agent_filters[filters.user_agent_count].mode = (filter_type == 2) ? FILTER_EXCLUDE : FILTER_INCLUDE; + filters.user_agent_count++; + printf("✅ User-Agent Filter hinzugefügt. Total: %d\n", filters.user_agent_count); + } else { + printf("FEHLER: Ungültiger Filter-Typ!\n"); + } + } else { + printf("FEHLER: Ungültiger Suchtext!\n"); + clear_input_buffer(); + } + + } else if (choice == 5) { return; } else if (choice != -1) { - printf("Ungültige Auswahl! Bitte wählen Sie 1-4.\n"); + printf("Ungültige Auswahl! Bitte wählen Sie 1-5.\n"); } } } void menu_delete_filters() { - int total_filters = filters.status_count + filters.ip_count + filters.time_count; + int total_filters = filters.status_count + filters.ip_count + filters.time_count + filters.user_agent_count; // UPDATE THIS LINE if (total_filters == 0) { printf("Keine Filter gesetzt zum Löschen.\n"); @@ -1079,6 +1349,11 @@ void menu_delete_filters() { printf("%d. IP-Adresse: %s %s\n", filter_index++, filters.ip_filters[i].ip_address, mode_str); } + for (int i = 0; i < filters.user_agent_count; i++) { + char* mode_str = (filters.user_agent_filters[i].mode == FILTER_EXCLUDE) ? "(ausschließen)" : "(einschließen)"; + printf("%d. User-Agent: \"%s\" %s\n", filter_index++, filters.user_agent_filters[i].pattern, mode_str); + } + for (int i = 0; i < filters.time_count; i++) { char* mode_str = (filters.time_filters[i].mode == FILTER_EXCLUDE) ? "(ausschließen)" : "(einschließen)"; printf("%d. Zeitraum: %02d.%02d.%d %02d:%02d:%02d - %02d.%02d.%d %02d:%02d:%02d %s\n", @@ -1136,6 +1411,18 @@ void menu_delete_filters() { current_index++; } + for (int i = 0; i < filters.user_agent_count; i++) { + if (current_index == choice) { + for (int j = i; j < filters.user_agent_count - 1; j++) { + filters.user_agent_filters[j] = filters.user_agent_filters[j + 1]; + } + filters.user_agent_count--; + printf("✅ User-Agent Filter gelöscht.\n"); + return; + } + current_index++; + } + for (int i = 0; i < filters.time_count; i++) { if (current_index == choice) { for (int j = i; j < filters.time_count - 1; j++) { @@ -1173,6 +1460,7 @@ void menu_reset_filters() { filters.status_count = 0; filters.ip_count = 0; filters.time_count = 0; + filters.user_agent_count = 0; filters.combination_mode = 0; printf("✅ Alle Filter zurückgesetzt.\n"); @@ -1215,10 +1503,11 @@ void menu_show_entries() { printf("\n=== EINTRÄGE ANZEIGEN ===\n"); int filtered_count = count_filtered_entries(); printf("Aktuell %d gefilterte Einträge verfügbar.\n", filtered_count); - printf("1. Gefilterte Einträge vollständig anzeigen\n"); - printf("2. Gefilterte Einträge in Datei exportieren\n"); - printf("3. Top 10 IP-Adressen anzeigen\n"); - printf("4. Zurück zum Hauptmenü\n"); + printf("1. Vollständig anzeigen\n"); + printf("2. Zu .csv exportieren (Timesketch-kompatibel)\n"); + printf("3. Top 10 IP-Adressen\n"); + printf("4. Top 10 User-Agents\n"); + printf("5. Zurück zum Hauptmenü\n"); printf("Auswahl: "); choice = read_safe_integer(); @@ -1239,6 +1528,8 @@ void menu_show_entries() { } else if (choice == 3) { show_top_10_ips(); } else if (choice == 4) { + show_top_user_agents(); + } else if (choice == 5) { return; } else if (choice != -1) { printf("Ungültige Auswahl! Bitte wählen Sie 1-4.\n"); @@ -1249,7 +1540,7 @@ void menu_show_entries() { void show_main_menu() { printf("\nHauptmenü:\n"); printf("1. Filter verwalten\n"); - printf("2. Gefilterte Einträge anzeigen\n"); + printf("2. Anzeigemodi\n"); printf("3. Statistiken anzeigen\n"); printf("4. Beenden\n"); printf("Auswahl: ");