diff --git a/bin/main b/bin/main index 43dc4e1..c385323 100755 Binary files a/bin/main and b/bin/main differ diff --git a/src/main.c b/src/main.c index 813acf5..5e50a35 100644 --- a/src/main.c +++ b/src/main.c @@ -34,7 +34,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND #define SUSPICIOUS_REQUEST_LEN_THRESHOLD 256 // 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 { +struct simple_time_t { int day; int month; int year; @@ -44,13 +44,13 @@ struct simple_time { }; // Struktur für die Darstellung eines Standard-NGINX-Logeintrags. -struct log_entry { +struct log_entry_t { 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_REQUEST_LENGTH]; // Pfade können lang werden, insbesodere bei base64-Strings wie oft in Malware verwendet int status_code; int bytes_sent; - struct simple_time time; + struct simple_time_t time; char referrer[128]; char user_agent[256]; char source_file[256]; @@ -60,92 +60,90 @@ struct log_entry { }; // Struktur für einen Status-Filtereintrag mit Inhalt & Modus -struct status_filter { +struct status_filter_t { int code; int filter_exclude_flag; }; -struct method_filter { +struct method_filter_t { char pattern[10]; int filter_exclude_flag; }; // für IP-Adressen -struct ip_filter { +struct ip_filter_t { char ip_address[50]; int filter_exclude_flag; }; // für Zeit, Start- und Endzeit -struct time_filter { - struct simple_time start_time; - struct simple_time end_time; +struct time_filter_t { + struct simple_time_t start_time; + struct simple_time_t end_time; int filter_exclude_flag; }; // Filter für User-Agent -struct user_agent_filter { +struct user_agent_filter_t { char pattern[256]; int filter_exclude_flag; }; // Filter für URL-Pfad/Request -struct url_filter { +struct url_filter_t { char pattern[MAX_REQUEST_LENGTH]; int filter_exclude_flag; }; -struct annotation_flag_filter { +struct annotation_flag_filter_t { int annotation_flag_is_present; int filter_exclude_flag; }; -struct annotation_filter { +struct annotation_filter_t { char pattern[64]; int filter_exclude_flag; }; // Struktur zum erhalten aller Filtereinträge, kann im Dialogbetrieb bearbeitet werden. Mit Zähler. -struct filter_system { - struct status_filter status_filters[MAX_FILTERS]; +struct filter_system_t { + struct status_filter_t status_filters[MAX_FILTERS]; int status_count; - struct method_filter method_filters[MAX_FILTERS]; + struct method_filter_t method_filters[MAX_FILTERS]; int method_count; - struct ip_filter ip_filters[MAX_FILTERS]; + struct ip_filter_t ip_filters[MAX_FILTERS]; int ip_count; - struct time_filter time_filters[MAX_FILTERS]; + struct time_filter_t time_filters[MAX_FILTERS]; int time_count; - struct user_agent_filter user_agent_filters[MAX_FILTERS]; + struct user_agent_filter_t user_agent_filters[MAX_FILTERS]; int user_agent_count; - struct url_filter url_filters[MAX_FILTERS]; + struct url_filter_t url_filters[MAX_FILTERS]; int url_count; - struct annotation_flag_filter annotation_flag_filter; + struct annotation_flag_filter_t annotation_flag_filter; int annotation_flag_filter_enabled; - struct annotation_filter annotation_filters[MAX_FILTERS]; + struct annotation_filter_t annotation_filters[MAX_FILTERS]; int annotation_count; - - int combination_mode; // 0=AND-Filter oder 1=OR-Filter }; // Definition einer Datenstruktur für die IP-Adressen Topliste -struct ip_stat { +struct ip_stat_t { char ip_address[50]; int count; }; // Initialisierung eines Arrays für die Logeinträge und weiterer Startvariablen -struct log_entry *all_entries = NULL; +struct log_entry_t *all_entries = NULL; int max_entries = 0; int total_entries = 0; int suspicious_patterns_count = 0; -struct filter_system filters = {0}; +struct filter_system_t filters = {0}; // für -v option int flag_verbose = 0; @@ -197,7 +195,7 @@ int month_name_to_number(char* month_name) { } // Vergleich von Zeitstempel-Strukturen - time_checkvalue ist der Prüfwert, time_filtervalue ist der Vergleichswert (Filter) - Funktion wird in time_matches() aufgerufen -int compare_times(struct simple_time time_checkvalue, struct simple_time time_filtervalue) { +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. @@ -238,7 +236,7 @@ int read_safe_integer(){ // Speicher freigeben und mit 0 überschreiben (Prävention von use-after-free-Schwachstelle) void cleanup_memory(){ if (all_entries != NULL) { - printf("\nDEBUG: %lu Bytes Speicher werden freigegeben\n", (unsigned long)(max_entries * sizeof(struct log_entry))); + printf("\nDEBUG: %lu Bytes Speicher werden freigegeben\n", (unsigned long)(max_entries * sizeof(struct log_entry_t))); free(all_entries); all_entries = NULL; } @@ -262,30 +260,30 @@ void mem_expand_dynamically(){ if (flag_verbose) printf("DEBUG: Dynamische Speichererweiterung von %d auf %d Einträge um Faktor %f\n", old_max, max_entries, GROWTH_FACTOR); - struct log_entry *new_ptr = realloc(all_entries, max_entries * sizeof(struct log_entry)); + struct log_entry_t *new_ptr = realloc(all_entries, max_entries * sizeof(struct log_entry_t)); if (new_ptr == NULL) { 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))); + printf("ERROR: Benötigter Speicher: %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry_t))); cleanup_and_exit(); } all_entries = new_ptr; - if (flag_verbose) printf("DEBUG: Speicher erfolgreich erweitert auf %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry))); + if (flag_verbose) printf("DEBUG: Speicher erfolgreich erweitert auf %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry_t))); } } void allocate_initial_memory(){ max_entries = INITIAL_ENTRIES; // Startwert 1000, globale Variable - all_entries = malloc(max_entries * sizeof(struct log_entry)); + all_entries = malloc(max_entries * sizeof(struct log_entry_t)); if (all_entries == NULL) { printf("ERROR: Konnte %d Einträge nicht allozieren, ..\n", max_entries); - printf("ERROR: %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry))); + printf("ERROR: %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry_t))); exit(1); // cleanup_and_exit() nicht nötig, da der Speicherbereich nicht beschrieben wurde - use-after-free unproblematisch } - if (flag_verbose) printf("DEBUG: Speicher erfolgreich alloziert für %d Log-Einträge (%lu Bytes)\n", max_entries, (unsigned long)(max_entries * sizeof(struct log_entry))); + if (flag_verbose) printf("DEBUG: Speicher erfolgreich alloziert für %d Log-Einträge (%lu Bytes)\n", max_entries, (unsigned long)(max_entries * sizeof(struct log_entry_t))); } void get_current_timestamp(char* buffer, int buffer_size) { @@ -380,7 +378,6 @@ int search_in_string(char* raw_string, char* search_string) { } } - // Fügt eine Annotation zu einem bestimmten Eintrag hinzu // https://stackoverflow.com/questions/5901181/c-string-append void annotate_entry(int index, char* annotation_string) { @@ -405,10 +402,8 @@ void annotate_entry(int index, char* annotation_string) { } } - - // TRANSPARENZ: Diese Funktion ist KI-generiert -void annotate_suspicious_entries(struct log_entry* dataset) { +void annotate_suspicious_entries(struct log_entry_t* dataset) { if (flag_verbose) printf("DEBUG: Prüfe %d Einträge auf verdächtige Muster...\n", total_entries); for (int i = 0; i < total_entries; i++) { @@ -730,7 +725,8 @@ int parse_simple_log_line(char* line, int entry_index, char* source_file) { // N if (is_valid_method) { // Normal parsen: HTTP-Methode bis zum nächsten Leerzeichen einlesen und speichern - strcpy(all_entries[entry_index].request_method, temp_method); + strncpy(all_entries[entry_index].request_method, temp_method, sizeof(all_entries[entry_index].request_method) - 1); + all_entries[entry_index].request_method[sizeof(all_entries[entry_index].request_method) - 1] = '\0'; while (*current_pos != ' ' && *current_pos != '\0') { current_pos++; } @@ -753,7 +749,8 @@ int parse_simple_log_line(char* line, int entry_index, char* source_file) { // N } 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 "ATYPICAL" repräsentiert - strcpy(all_entries[entry_index].request_method, "ATYPICAL"); + strncpy(all_entries[entry_index].request_method, "ATYPICAL", sizeof(all_entries[entry_index].request_method) - 1); + all_entries[entry_index].request_method[sizeof(all_entries[entry_index].request_method) - 1] = '\0'; // Read entire quoted content into url_path for forensic analysis int i = 0; @@ -799,13 +796,18 @@ int parse_simple_log_line(char* line, int entry_index, char* source_file) { // N } current_pos = skip_spaces(current_pos); - // Parsen des Referrer-Feldes innerhalb "", wird übersprungen da nicht gespeichert + // Parsen des Referrer-Feldes innerhalb "" if (*current_pos == '"') { current_pos++; // öffnendes Anführungszeichen überspringen - // Referrer-Inhalt bis zum schließenden Anführungszeichen überspringen - while (*current_pos != '"' && *current_pos != '\0') { + // Referrer-Inhalt zwischen "" einlesen + int i = 0; + while (*current_pos != '"' && *current_pos != '\0' && i < sizeof(all_entries[entry_index].referrer) - 1) { + all_entries[entry_index].referrer[i] = *current_pos; + i++; current_pos++; } + all_entries[entry_index].referrer[i] = '\0'; + 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.\nLogeintrag: %s\n", line); @@ -959,207 +961,144 @@ void load_log_file(char* path) { // die aufgerufene Funktion ist KI-generiert und annotiert verdächtige Requests automatisch. annotate_suspicious_entries(all_entries); if (flag_verbose) printf("DEBUG: Aktueller Speicherverbrauch: %lu Bytes für %d Einträge\n", - (unsigned long)(max_entries * sizeof(struct log_entry)), max_entries); + (unsigned long)(max_entries * sizeof(struct log_entry_t)), max_entries); } +// Simplified filter functions using cleaner logic from shorter version + // 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) { + // kein Filter gesetzt - positiver Rückgabewert if (filters.user_agent_count == 0) return 1; - // Ausschluss-Filter geht vor + + // Ausschluss-Filter, jeden iterativ prüfen for (int i = 0; i < filters.user_agent_count; i++) { if (filters.user_agent_filters[i].filter_exclude_flag == 1) { - int pattern_found = search_in_string(user_agent, filters.user_agent_filters[i].pattern); - if (pattern_found) { - return 0; // früheres Verlassen der Schleife, sobald Ausschlussfilter zutrifft + if (search_in_string(user_agent, filters.user_agent_filters[i].pattern)) { + return 0; // Ausschlussfilter aktiv, User Agent gefunden - nicht anzeigen } } } - // Prüfung im entsprechenden Modus - int include_count = 0; - int include_matches = 0; - + // Einschlussfilter + int has_include_filters = 0; for (int i = 0; i < filters.user_agent_count; i++) { if (filters.user_agent_filters[i].filter_exclude_flag == 0) { - include_count++; - int pattern_found = search_in_string(user_agent, filters.user_agent_filters[i].pattern); - if (pattern_found) { - include_matches++; - if (filters.combination_mode == 1) { // OR-Modus - return 1; // Früheres Verlassen der Schleife bei erstem zutreffendem Einschlussfilter im OR-Modus - } + has_include_filters = 1; + if (search_in_string(user_agent, filters.user_agent_filters[i].pattern)) { + return 1; // Ausschlussfilter inaktiv, Eintrag gefunden - anzeigen } } } - - // Diese Prüfung wird ausgeführt, wenn Einschlussfilter vorhanden sind - if (include_count > 0) { - if (filters.combination_mode == 0) { // AND-Modus - return include_matches == include_count; // Alle Einschlussfilter müssen zutreffen, die Treffer müssen Anzahl der Filter entsprechen - } else { // OR-Modus - return include_matches > 0; // Ausschlussfilter im ODER-Modus - wenn ein beliebiger Eintrag zum Ausschlussfilter passt, wird 0=negativ zurückgegeben - } - } - - return 1; // Keine Einschlussfilter, keine zutreffenden Ausschlussfilter, positiver Rückgabewert =1 + // Einschlussfilter vorhanden, aber keine gefunden + return !has_include_filters; } // Filterfunktion für URL-Pfad. Nimmt den Datensatz entgegen und prüft gegen die gesetzten Filter, gibt dann 0 oder 1 zurück int url_matches(char* url_path) { + // kein Filter gesetzt - positiver Rückgabewert if (filters.url_count == 0) return 1; - // Ausschluss-Filter geht vor + + // Ausschluss-Filter, jeden iterativ prüfen for (int i = 0; i < filters.url_count; i++) { if (filters.url_filters[i].filter_exclude_flag == 1) { - int pattern_found = search_in_string(url_path, filters.url_filters[i].pattern); - if (pattern_found) { - return 0; // früheres Verlassen der Schleife, sobald Ausschlussfilter zutrifft + if (search_in_string(url_path, filters.url_filters[i].pattern)) { + return 0; // Ausschlussfilter aktiv, URL-Pfad gefunden - nicht anzeigen } } } - // Prüfung im entsprechenden Modus - int include_count = 0; - int include_matches = 0; - + // Einschlussfilter + int has_include_filters = 0; for (int i = 0; i < filters.url_count; i++) { if (filters.url_filters[i].filter_exclude_flag == 0) { - include_count++; - int pattern_found = search_in_string(url_path, filters.url_filters[i].pattern); - if (pattern_found) { - include_matches++; - if (filters.combination_mode == 1) { // OR-Modus - return 1; // Früheres Verlassen der Schleife bei erstem zutreffendem Einschlussfilter im OR-Modus - } + has_include_filters = 1; + if (search_in_string(url_path, filters.url_filters[i].pattern)) { + return 1; // Ausschlussfilter inaktiv, Eintrag gefunden - anzeigen } } } - - // Diese Prüfung wird ausgeführt, wenn Einschlussfilter vorhanden sind - if (include_count > 0) { - if (filters.combination_mode == 0) { // AND-Modus - return include_matches == include_count; // Alle Einschlussfilter müssen zutreffen, die Treffer müssen Anzahl der Filter entsprechen - } else { // OR-Modus - return include_matches > 0; // Ausschlussfilter im ODER-Modus - wenn ein beliebiger Eintrag zum Ausschlussfilter passt, wird 0=negativ zurückgegeben - } - } - - return 1; // Keine Einschlussfilter, keine zutreffenden Ausschlussfilter, positiver Rückgabewert =1 + // Einschlussfilter vorhanden, aber keine gefunden + return !has_include_filters; } // Filterfunktion für Zugriffsmethode. Nimmt den Datensatz entgegen und prüft gegen die gesetzten Filter, gibt dann 0 oder 1 zurück int method_matches(char* request_method) { + // kein Filter gesetzt - positiver Rückgabewert if (filters.method_count == 0) return 1; - // Ausschluss-Filter geht vor + + // Ausschluss-Filter, jeden iterativ prüfen for (int i = 0; i < filters.method_count; i++) { if (filters.method_filters[i].filter_exclude_flag == 1) { - int pattern_found = search_in_string(request_method, filters.method_filters[i].pattern); - if (pattern_found) { - return 0; // früheres Verlassen der Schleife, sobald Ausschlussfilter zutrifft + if (search_in_string(request_method, filters.method_filters[i].pattern)) { + return 0; // Ausschlussfilter aktiv, HTTP-Methode gefunden - nicht anzeigen } } } - // Prüfung im entsprechenden Modus - int include_count = 0; - int include_matches = 0; - + // Einschlussfilter + int has_include_filters = 0; for (int i = 0; i < filters.method_count; i++) { if (filters.method_filters[i].filter_exclude_flag == 0) { - include_count++; - int pattern_found = search_in_string(request_method, filters.method_filters[i].pattern); - if (pattern_found) { - include_matches++; - if (filters.combination_mode == 1) { // OR-Modus - return 1; // Früheres Verlassen der Schleife bei erstem zutreffendem Einschlussfilter im OR-Modus - } + has_include_filters = 1; + if (search_in_string(request_method, filters.method_filters[i].pattern)) { + return 1; // Ausschlussfilter inaktiv, Eintrag gefunden - anzeigen } } } - - // Diese Prüfung wird ausgeführt, wenn Einschlussfilter vorhanden sind - if (include_count > 0) { - if (filters.combination_mode == 0) { // AND-Modus - return include_matches == include_count; // Alle Einschlussfilter müssen zutreffen, die Treffer müssen Anzahl der Filter entsprechen - } else { // OR-Modus - return include_matches > 0; - } - } - - return 1; // Keine Einschlussfilter, keine zutreffenden Ausschlussfilter, positiver Rückgabewert =1 + // Einschlussfilter vorhanden, aber keine gefunden + return !has_include_filters; } +// Filterfunktion für Status-Code. Nimmt den Datensatz entgegen und prüft gegen die gesetzten Filter, gibt dann 0 oder 1 zurück int status_code_matches(int status_code) { + // kein Filter gesetzt - positiver Rückgabewert if (filters.status_count == 0) return 1; - // Ausschluss-Filter prüfen: immer übergeordnet gültig + + // Ausschluss-Filter, jeden iterativ prüfen for (int i = 0; i < filters.status_count; i++) { if (filters.status_filters[i].filter_exclude_flag == 1) { - if (filters.status_filters[i].code == status_code) { - return 0; + if (status_code == filters.status_filters[i].code) { + return 0; // Ausschlussfilter aktiv, Status-Code gefunden - nicht anzeigen } } } - - // Filter prüfen in entsprechendem Modus - int include_count = 0; - int include_matches = 0; - + // Einschlussfilter + int has_include_filters = 0; for (int i = 0; i < filters.status_count; i++) { if (filters.status_filters[i].filter_exclude_flag == 0) { - include_count++; - if (filters.status_filters[i].code == status_code) { - include_matches++; - if (filters.combination_mode == 1) { // OR-Modus - return 1; // positiver Rückgabewert, Schleife wird verlassen sobald erster Treffer im OR-Modus - } + has_include_filters = 1; + if (status_code == filters.status_filters[i].code) { + return 1; // Ausschlussfilter inaktiv, Eintrag gefunden - anzeigen } } } - - // Diese Prüfung wird ausgeführt, wenn Einschlussfilter vorhanden sind - if (include_count > 0) { - if (filters.combination_mode == 0) { // UND-Modus - return include_matches == include_count; // Filter-Treffer müssen der Anzahl der Statuscode-Filter entsprechen - } else { // OR-Modus - return include_matches > 0; // Ausschlussfilter im ODER-Modus - wenn ein beliebiger Eintrag zum Ausschlussfilter passt, wird 0=negativ zurückgegeben - } - } - return 1; // Keine Einschlussfilter, keine Treffer in den Ausschlussfiltern, positiver Rückgabewert = 1 + // Einschlussfilter vorhanden, aber keine gefunden + return !has_include_filters; } +// Filterfunktion für IP-Adresse. Nimmt den Datensatz entgegen und prüft gegen die gesetzten Filter, gibt dann 0 oder 1 zurück int ip_address_matches(char* ip_address) { + // kein Filter gesetzt - positiver Rückgabewert if (filters.ip_count == 0) return 1; - // Prüfen der Ausschlussfilter, sind dem Rest vorgelagert + // Ausschluss-Filter, jeden iterativ prüfen for (int i = 0; i < filters.ip_count; i++) { if (filters.ip_filters[i].filter_exclude_flag == 1) { - if (strcmp(filters.ip_filters[i].ip_address, ip_address) == 0) { - return 0; // zutreffender Ausschlussfilter führt zu negativem Rückgabewert + if (search_in_string(ip_address, filters.ip_filters[i].ip_address)) { + return 0; // Ausschlussfilter aktiv, IP-Adresse gefunden - nicht anzeigen } } } - - // Prüfung im AND oder im OR-Modus - int include_count = 0; - int include_matches = 0; - + // Einschlussfilter + int has_include_filters = 0; for (int i = 0; i < filters.ip_count; i++) { if (filters.ip_filters[i].filter_exclude_flag == 0) { - include_count++; - if (strcmp(filters.ip_filters[i].ip_address, ip_address) == 0) { - include_matches++; - if (filters.combination_mode == 1) { // OR-Modus - return 1; // Früheres Verlassen der Schleife, sofern erster Filter im OR-Modus zutrifft - } + has_include_filters = 1; + if (search_in_string(ip_address, filters.ip_filters[i].ip_address)) { + return 1; // Ausschlussfilter inaktiv, Eintrag gefunden - anzeigen } } } - - // Diese Prüfung wird ausgeführt, wenn Einschlussfilter vorhanden sind - if (include_count > 0) { - if (filters.combination_mode == 0) { // UND-Modus - return include_matches == include_count; // Filter-Treffer müssen der Anzahl der IP-Adressen-Filter entsprechen - } else { // OR-Modus - return include_matches > 0; // zutreffender Ausschlussfilter führt zu negativem Rückgabewert - } - } - return 1; // Keine Einschlussfilter, keine Treffer in den Ausschlussfiltern, positiver Rückgabewert = 1 + // Einschlussfilter vorhanden, aber keine gefunden + return !has_include_filters; } int is_annotated(int annotated_flag){ @@ -1177,49 +1116,35 @@ int is_annotated(int annotated_flag){ return 1; } - int annotation_matches(char* annotation) { + // kein Filter gesetzt - positiver Rückgabewert if (filters.annotation_count == 0) return 1; - // Ausschluss-Filter geht vor + + // Ausschluss-Filter, jeden iterativ prüfen for (int i = 0; i < filters.annotation_count; i++) { if (filters.annotation_filters[i].filter_exclude_flag == 1) { - int pattern_found = search_in_string(annotation, filters.annotation_filters[i].pattern); - if (pattern_found) { - return 0; // früheres Verlassen der Schleife, sobald Ausschlussfilter zutrifft + if (search_in_string(annotation, filters.annotation_filters[i].pattern)) { + return 0; // Ausschlussfilter aktiv, Annotation gefunden - nicht anzeigen } } } - // Prüfung im entsprechenden Modus - int include_count = 0; - int include_matches = 0; - + // Einschlussfilter + int has_include_filters = 0; for (int i = 0; i < filters.annotation_count; i++) { if (filters.annotation_filters[i].filter_exclude_flag == 0) { - include_count++; - int pattern_found = search_in_string(annotation, filters.annotation_filters[i].pattern); - if (pattern_found) { - include_matches++; - if (filters.combination_mode == 1) { // OR-Modus - return 1; // Früheres Verlassen der Schleife bei erstem zutreffendem Einschlussfilter im OR-Modus - } + has_include_filters = 1; + if (search_in_string(annotation, filters.annotation_filters[i].pattern)) { + return 1; // Ausschlussfilter inaktiv, Eintrag gefunden - anzeigen } } } - - // Diese Prüfung wird ausgeführt, wenn Einschlussfilter vorhanden sind - if (include_count > 0) { - if (filters.combination_mode == 0) { // AND-Modus - return include_matches == include_count; // Alle Einschlussfilter müssen zutreffen, die Treffer müssen Anzahl der Filter entsprechen - } else { // OR-Modus - return 0; - } - } - - return 1; // Keine Einschlussfilter, keine zutreffenden Ausschlussfilter, positiver Rückgabewert =1 + // Einschlussfilter vorhanden, aber keine gefunden + 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. -int time_matches(struct simple_time entry_time) { +int time_matches(struct simple_time_t entry_time) { + // kein Filter gesetzt - positiver Rückgabewert if (filters.time_count == 0) return 1; // Übergeordneter Ausschlussfilter @@ -1233,100 +1158,36 @@ int time_matches(struct simple_time entry_time) { } } - // Prüfung im entsprechenden Modus - int include_count = 0; - int include_matches = 0; + // Einschlussfilter + int has_include_filters = 0; for (int i = 0; i < filters.time_count; i++) { if (filters.time_filters[i].filter_exclude_flag == 0) { - include_count++; + 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) { - include_matches++; - if (filters.combination_mode == 1) { // OR-Modus - return 1; // Sobald der erste Zeitraum im OR-Modus zutrifft, wird die Schleife verlassen - } + return 1; // Einschlussfilter passt } } } - // Diese Prüfung wird ausgeführt, wenn Einschlussfilter vorhanden sind - if (include_count > 0) { - if (filters.combination_mode == 0) { // AND-Modus - return include_matches == include_count; // Filter-Treffer müssen der Anzahl der Zeitraum-Filter entsprechen - } else { // OR-Modus - return include_matches > 0; // zutreffender Ausschlussfilter führt zu negativem Rückgabewert - } - } - return 1; // keine Einschlussfilter und keine zutreffenden Ausschlussfilter - positiver Rückgabewert + // Wenn Einschlussfilter vorhanden sind, aber keiner passt + return !has_include_filters; } -// Prüfen aller Filter im AND-Modus oder OR-Modus (combination_mode) pro Log-Eintrag +// Simplified passes_filter function using AND logic only int passes_filter(int entry_index) { - if (filters.combination_mode == 0) { // AND-Modus - // alle AND-verknüpften Filter müssen zutreffen - int status_match = status_code_matches(all_entries[entry_index].status_code); - int method_match = method_matches(all_entries[entry_index].request_method); - 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); - int url_match = url_matches(all_entries[entry_index].url_path); - int has_annotation = is_annotated(all_entries[entry_index].annotated_flag); - int annotation_match = annotation_matches(all_entries[entry_index].annotation); - - return status_match && ip_match && time_match && user_agent_match && method_match && url_match && has_annotation && annotation_match; - } else { // OR-Modus - // für den geprüften Eintrag muss mindestens eine der Filterkategorien zutreffen. - // Eine Prüfung über Filter1 || Filter2 || etc ist nicht möglich, da auch - // nicht gesetzte Filter hier keine Einschränkng bilden. Daher muss jede KAtegorie einzeln geprüft werden. - 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; - if (total_filters == 0) return 1; - - int has_passing_filter = 0; - - // Jede Filterkategorie wird nur geprüft, wenn auch entsprechende Filter gesetzt sind. - if (filters.status_count > 0) { - if (status_code_matches(all_entries[entry_index].status_code)) { - has_passing_filter = 1; - } - } - if (filters.method_count > 0) { - if (method_matches(all_entries[entry_index].request_method)) { - has_passing_filter = 1; - } - } - if (filters.ip_count > 0) { - if (ip_address_matches(all_entries[entry_index].ip_address)) { - has_passing_filter = 1; - } - } - if (filters.time_count > 0) { - if (time_matches(all_entries[entry_index].time)) { - has_passing_filter = 1; - } - } - if (filters.user_agent_count > 0) { - if (user_agent_matches(all_entries[entry_index].user_agent)) { - has_passing_filter = 1; - } - } - if (filters.url_count > 0) { - if (url_matches(all_entries[entry_index].url_path)) { - has_passing_filter = 1; - } - } - if (filters.annotation_flag_filter_enabled){ - if (is_annotated(all_entries[entry_index].annotated_flag)) { - has_passing_filter = 1; - } - } - if (filters.annotation_count > 0){ - if (annotation_matches(all_entries[entry_index].annotation)){ - has_passing_filter = 1; - } - } - return has_passing_filter; - } + // Simple AND logic - all filters must pass + int status_match = status_code_matches(all_entries[entry_index].status_code); + int method_match = method_matches(all_entries[entry_index].request_method); + 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); + int url_match = url_matches(all_entries[entry_index].url_path); + int has_annotation = is_annotated(all_entries[entry_index].annotated_flag); + int annotation_match = annotation_matches(all_entries[entry_index].annotation); + + return status_match && ip_match && time_match && user_agent_match && method_match && url_match && has_annotation && annotation_match; } // Einfacher Zähler für alle Einträge, die die Filterfunktionen bestehen @@ -1341,7 +1202,7 @@ int count_filtered_entries(){ } // notwendig, um konformes Timestamp-Format aus simple_time struct zu generieren. Unterstützt derzeit nur UTC -void format_iso8601_time(struct simple_time time, char* buffer, int buffer_size) { +void format_iso8601_time(struct simple_time_t time, char* buffer, int buffer_size) { snprintf(buffer, buffer_size, "%04d-%02d-%02dT%02d:%02d:%02d+00:00", time.year, time.month, time.day, time.hour, time.minute, time.second); } @@ -1382,8 +1243,8 @@ void export_filtered_entries(char *filepath) { return; } - // CSV-Kopfzeile für Timesketch-Kompatibilität - fprintf(file, "datetime,message,timestamp_desc,ip_address,method,url_path,status_code,bytes_sent,user_agent,parsing_timestamp,tag\n"); + // CSV-Kopfzeile für Timesketch-Kompatibilität (mit Referrer hinzugefügt) + fprintf(file, "datetime,message,timestamp_desc,ip_address,method,url_path,status_code,bytes_sent,referrer,user_agent,parsing_timestamp,tag\n"); int exported_count = 0; char iso_datetime[32]; @@ -1434,6 +1295,8 @@ void export_filtered_entries(char *filepath) { fprintf(file, ","); fprintf(file, "%d", all_entries[i].bytes_sent); fprintf(file, ","); + write_csv_field(file, all_entries[i].referrer); + fprintf(file, ","); write_csv_field(file, all_entries[i].user_agent); fprintf(file, ","); write_csv_field(file, all_entries[i].parsing_timestamp); @@ -1451,7 +1314,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"); @@ -1493,7 +1355,7 @@ void show_annotated_entries() { // 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) - struct ip_stat ip_stats[1000]; + struct ip_stat_t ip_stats[1000]; // lokaler Zähler int unique_ips = 0; @@ -1521,7 +1383,8 @@ void show_top_x_ips(){ } else { // wenn nicht gefunden (found_index == -1) wird die IP mit count = 1 neu angelegt if (unique_ips < 1000) { - strcpy(ip_stats[unique_ips].ip_address, current_ip); + strncpy(ip_stats[unique_ips].ip_address, current_ip, sizeof(ip_stats[unique_ips].ip_address) - 1); + ip_stats[unique_ips].ip_address[sizeof(ip_stats[unique_ips].ip_address) - 1] = '\0'; ip_stats[unique_ips].count = 1; unique_ips++; } @@ -1533,7 +1396,7 @@ void show_top_x_ips(){ for (int i = 0; i < unique_ips - 1; i++) { for (int j = 0; j < unique_ips - i - 1; j++) { if (ip_stats[j].count < ip_stats[j + 1].count) { - struct ip_stat temp = ip_stats[j]; + struct ip_stat_t temp = ip_stats[j]; ip_stats[j] = ip_stats[j + 1]; ip_stats[j + 1] = temp; } @@ -1559,12 +1422,12 @@ void show_top_x_ips(){ // gleiche Mechanik wie in show_top_x_ips - könnte wahrscheinlich vereinheitlicht werden void show_top_user_agents(){ - struct user_agent_stat { + struct user_agent_stat_t { char user_agent[256]; int count; }; - struct user_agent_stat agent_stats[1000]; + struct user_agent_stat_t agent_stats[1000]; int unique_agents = 0; for (int i = 0; i < total_entries; i++) { @@ -1584,7 +1447,8 @@ void show_top_user_agents(){ agent_stats[found_index].count++; } else { if (unique_agents < 1000) { - strcpy(agent_stats[unique_agents].user_agent, current_agent); + strncpy(agent_stats[unique_agents].user_agent, current_agent, sizeof(agent_stats[unique_agents].user_agent) - 1); + agent_stats[unique_agents].user_agent[sizeof(agent_stats[unique_agents].user_agent) - 1] = '\0'; agent_stats[unique_agents].count = 1; unique_agents++; } @@ -1594,7 +1458,7 @@ void show_top_user_agents(){ 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]; + struct user_agent_stat_t temp = agent_stats[j]; agent_stats[j] = agent_stats[j + 1]; agent_stats[j + 1] = temp; } @@ -1759,14 +1623,7 @@ void print_filter_args(){ printf(" "); } - if (filters.combination_mode == 1) { - printf("--mode=or "); - }else { - printf("--mode=and "); - } - printf("\n"); - } // Status-Anzeige, die in jedem Bildschirm dynamisch eine Vorschau des gefilterten Datensatzes, sowie eine Übersicht über die gesetzten Filter anzeigt. @@ -1778,8 +1635,7 @@ void show_status(){ if (total_entries > 0) { printf(" %d Logzeilen in Datenstruktur\n", total_entries); - //nicht verifiziert? - printf(" Speicherbelegung: %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry))); + printf(" Speicherbelegung: %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry_t))); } else { printf(" ERROR: Keine Einträge in Datenstruktur!\n"); } @@ -1794,18 +1650,13 @@ void show_status(){ print_filter_args(); printf("\n \n"); printf(" Ausschlussfilter (!) haben Vorrang, dann Einschlussfilter\n"); - - int active_types = (filters.status_count > 0) + (filters.method_count > 0) + (filters.ip_count > 0) + (filters.user_agent_count > 0) + (filters.time_count > 0) + (filters.url_count > 0) + (filters.annotation_flag_filter_enabled > 0) + (filters.annotation_count > 0); - if (active_types > 1) { - printf(" %s-Verknüpfung (nur Einschlussfilter, Ausschlussfilter sind stets ODER-verknüpft)\n", filters.combination_mode == 0 ? "UND" : "ODER"); - } + printf(" Filter-Logik: UND-Verknüpfung zwischen Kategorien\n"); } if (total_entries > 0) { int filtered_count = count_filtered_entries(); printf("\n DATENSATZ: \n %d von %d Einträgen entsprechen den Filtern\n", filtered_count, total_entries); printf(" Außergewöhnliche Muster gefunden: %d\n", suspicious_patterns_count); - } } @@ -1817,9 +1668,7 @@ void print_filter_examples(){ printf("GRUNDLEGENDE SYNTAX:\n"); printf(" --filtertyp=wert1,wert2,wert3 Mehrere Werte kommagetrennt\n"); printf(" --filtertyp=!wert Ausschluss (mit ! Präfix)\n"); - printf(" --filtertyp=wert1,!wert2 Gemischt: wert1 einschließen, wert2 ausschließen\n"); - printf(" --mode=and UND-Verknüpfung (Standard)\n"); - printf(" --mode=or ODER-Verknüpfung\n\n"); + printf(" --filtertyp=wert1,!wert2 Gemischt: wert1 einschließen, wert2 ausschließen\n\n"); printf("VERFÜGBARE FILTER:\n"); printf(" --status=200,404,500 HTTP-Status-Codes\n"); @@ -1838,27 +1687,27 @@ void print_filter_examples(){ printf("FILTER-LOGIK:\n"); printf(" 1. Ausschlussfilter (!) haben IMMER Vorrang\n"); - printf(" 2. Bei mehreren Filtern verschiedener Typen: Kombination nach --mode\n"); - printf(" 3. Bei mehreren Filtern gleichen Typs: Kombination nach --mode\n\n"); + printf(" 2. UND-Verknüpfung zwischen verschiedenen Filtertypen\n"); + printf(" 3. ODER-Verknüpfung innerhalb gleicher Filtertypen\n\n"); printf("PRAKTISCHE BEISPIELE:\n\n"); printf("SICHERHEITSANALYSE:\n"); printf("Verdächtige Pfad-Zugriffe finden:\n"); - printf(" --url=.git,.env,wp-admin,phpmyadmin --mode=or\n"); + printf(" --url=.git,.env,wp-admin,phpmyadmin\n"); printf(" > Zeigt Zugriffe auf Git-Repos, Config-Dateien oder Admin-Bereiche\n\n"); printf("SQL-Injection Versuche:\n"); - printf(" --annotation=SQL --url=select,union,drop --mode=or\n"); + printf(" --annotation=SQL --url=select,union,drop\n"); printf(" > Kombiniert automatische Erkennung mit manuellen Mustern\n\n"); printf("Bot-Traffic ausschließen:\n"); - printf(" --useragent=!bot,!crawler,!spider --status=200 --mode=and\n"); + printf(" --useragent=!bot,!crawler,!spider --status=200\n"); printf(" > Nur erfolgreiche Requests ohne Bot-Traffic\n\n"); printf("PERFORMANCE-ANALYSE:\n"); printf("Server-Fehler in Geschäftszeiten:\n"); - printf(" --timerange=2025-08-31-08-00-00:2025-08-31-18-00-00 --status=500,502,503 --mode=and\n"); + printf(" --timerange=2025-08-31-08-00-00:2025-08-31-18-00-00 --status=500,502,503\n"); printf(" > 5xx Fehler nur während Arbeitszeit\n\n"); printf("Wartungszeit ausschließen:\n"); @@ -1871,38 +1720,38 @@ void print_filter_examples(){ printf(" > Zeigt alle automatisch erkannten Anomalien\n\n"); printf("Lange Payloads von bestimmten IPs:\n"); - printf(" --annotation=Long --ip=!192.168.1.0 --mode=and\n"); + printf(" --annotation=Long --ip=!192.168.1.0\n"); printf(" > Verdächtig lange Requests von externen IPs\n\n"); printf("FORENSIK:\n"); printf("Angriffsmuster in kritischem Zeitfenster:\n"); - printf(" --timerange=2025-08-31-14-30-00:2025-08-31-15-30-00 --status=403,404 --annotation=Scanner,SQL,XSS --mode=and\n"); + printf(" --timerange=2025-08-31-14-30-00:2025-08-31-15-30-00 --status=403,404 --annotation=Scanner,SQL,XSS\n"); printf(" > Detaillierte Analyse eines Sicherheitsvorfalls\n\n"); printf("Fehlgeschlagene Login-Versuche:\n"); - printf(" --url=login,signin,auth --status=401,403 --annotation=Failed --mode=and\n"); + printf(" --url=login,signin,auth --status=401,403 --annotation=Failed\n"); printf(" > Brute-Force Angriffe auf Login-Bereiche\n\n"); printf("TRAFFIC-BEREINIGUNG:\n"); printf("Nur menschlicher Traffic:\n"); - printf(" --useragent=!bot,!crawler,!scanner --method=!PROPFIND --status=200,304 --mode=and\n"); + printf(" --useragent=!bot,!crawler,!scanner --method=!PROPFIND --status=200,304\n"); printf(" > Erfolgreiche Requests ohne automatisierten Traffic\n\n"); printf("Administrative Zugriffe ausschließen:\n"); - printf(" --ip=!192.168.1.10,!10.0.0.100 --url=!admin,!manager --mode=and\n"); + printf(" --ip=!192.168.1.10,!10.0.0.100 --url=!admin,!manager\n"); printf(" > Traffic ohne Admin-IPs und Admin-Bereiche\n\n"); printf("HÄUFIGE KOMBINATIONEN:\n"); printf("DDoS-Verdacht:\n"); - printf(" --status=429,503,502 --annotation=Rate --mode=or\n"); + printf(" --status=429,503,502 --annotation=Rate\n"); printf(" > Überlastungsindikationen und Rate-Limiting\n\n"); printf("Webshell-Suche:\n"); - printf(" --url=.php,cmd=,exec= --annotation=Shell --method=POST,GET --mode=or\n"); + printf(" --url=.php,cmd=,exec= --annotation=Shell --method=POST,GET\n"); printf(" > Verdächtige PHP-Aufrufe und Shell-Aktivitäten\n\n"); printf("Normale Website-Nutzung:\n"); - printf(" --status=200,304 --method=GET,POST --useragent=!bot --annotated=!true --mode=and\n"); + printf(" --status=200,304 --method=GET,POST --useragent=!bot --annotated=!true\n"); printf(" > Sauberer, erfolgreicher Website-Traffic ohne Anomalien\n\n"); printf("ANNOTATIONEN (automatisch erkannt):\n"); @@ -2015,7 +1864,6 @@ int read_menu_input(){ return (int)number; } - // Transparenz: Die visuelle Darstellung der Menus und dessen Formatierung, sowie repetitive schriftliche Wiedergabe wie etwa die interaktive Eingabe der Zeiträume ist teilweise KI-generiert. void show_main_menu(){ printf("\nHAUPTMENÜ\n"); @@ -2086,7 +1934,8 @@ int menu_set_filters(){ int filter_type = safe_read_integer("Auswahl: ", 1, 2); if (filter_type < 0) continue; - strcpy(filters.ip_filters[filters.ip_count].ip_address, ip); + strncpy(filters.ip_filters[filters.ip_count].ip_address, ip, sizeof(filters.ip_filters[filters.ip_count].ip_address) - 1); + filters.ip_filters[filters.ip_count].ip_address[sizeof(filters.ip_filters[filters.ip_count].ip_address) - 1] = '\0'; filters.ip_filters[filters.ip_count].filter_exclude_flag = (filter_type == 2) ? 1 : 0; filters.ip_count++; printf("IP-Filter hinzugefügt. Gesamt: %d\n", filters.ip_count); @@ -2098,7 +1947,7 @@ int menu_set_filters(){ } printf("\nZEITRAUM FILTER HINZUFÜGEN\n"); - struct time_filter new_time_filter = {0}; + struct time_filter_t new_time_filter = {0}; printf("STARTZEIT:\n"); int start_year = safe_read_integer("Jahr (z.B. 2025): ", 1970, 2100); @@ -2120,7 +1969,7 @@ int menu_set_filters(){ if (start_second < 0) continue; printf("\nENDZEIT:\n"); - int end_year = safe_read_integer("Jahr (z.B. 2023): ", 1970, 2100); + int end_year = safe_read_integer("Jahr (z.B. 2025): ", 1970, 2100); if (end_year < 0) continue; int end_month = safe_read_integer("Monat (1-12): ", 1, 12); @@ -2183,7 +2032,8 @@ int menu_set_filters(){ int filter_type = safe_read_integer("Auswahl: ", 1, 2); if (filter_type < 0) continue; - strcpy(filters.user_agent_filters[filters.user_agent_count].pattern, pattern); + strncpy(filters.user_agent_filters[filters.user_agent_count].pattern, pattern, sizeof(filters.user_agent_filters[filters.user_agent_count].pattern) - 1); + filters.user_agent_filters[filters.user_agent_count].pattern[sizeof(filters.user_agent_filters[filters.user_agent_count].pattern) - 1] = '\0'; filters.user_agent_filters[filters.user_agent_count].filter_exclude_flag = (filter_type == 2) ? 1 : 0; filters.user_agent_count++; printf("User-Agent Filter hinzugefügt. Gesamt: %d\n", filters.user_agent_count); @@ -2205,7 +2055,8 @@ int menu_set_filters(){ int filter_type = safe_read_integer("Auswahl: ", 1, 2); if (filter_type < 0) continue; - strcpy(filters.method_filters[filters.method_count].pattern, pattern); + strncpy(filters.method_filters[filters.method_count].pattern, pattern, sizeof(filters.method_filters[filters.method_count].pattern) - 1); + filters.method_filters[filters.method_count].pattern[sizeof(filters.method_filters[filters.method_count].pattern) - 1] = '\0'; filters.method_filters[filters.method_count].filter_exclude_flag = (filter_type == 2) ? 1 : 0; filters.method_count++; printf("Method-Filter hinzugefügt. Gesamt: %d\n", filters.method_count); @@ -2227,10 +2078,12 @@ int menu_set_filters(){ int filter_type = safe_read_integer("Auswahl: ", 1, 2); if (filter_type < 0) continue; - strcpy(filters.url_filters[filters.url_count].pattern, pattern); + strncpy(filters.url_filters[filters.url_count].pattern, pattern, sizeof(filters.url_filters[filters.url_count].pattern) - 1); + filters.url_filters[filters.url_count].pattern[sizeof(filters.url_filters[filters.url_count].pattern) - 1] = '\0'; filters.url_filters[filters.url_count].filter_exclude_flag = (filter_type == 2) ? 1 : 0; filters.url_count++; printf("URL-Filter hinzugefügt. Gesamt: %d\n", filters.url_count); + } else if (choice == 7) { printf("\nFilter-Typ wählen:\n"); printf("1. Einschließen (nur Einträge mit Annotationen)\n"); @@ -2240,14 +2093,16 @@ int menu_set_filters(){ if (filter_type < 0) continue; filters.annotation_flag_filter.filter_exclude_flag = (filter_type == 2) ? 1 : 0; filters.annotation_flag_filter_enabled = 1; + printf("Annotations-Flag Filter hinzugefügt.\n"); + } else if (choice == 8) { if (filters.annotation_count >= MAX_FILTERS) { printf("ERROR: Maximale Anzahl Annotations-Filter erreicht (%d)!\n", MAX_FILTERS); continue; } - char pattern[MAX_REQUEST_LENGTH]; - int result = safe_read_string("Annotations-Suchtext eingeben (z.B. 'long request'): ", pattern, sizeof(pattern)); + char pattern[64]; + int result = safe_read_string("Annotations-Suchtext eingeben (z.B. 'Long Payload', 'SQL'): ", pattern, sizeof(pattern)); if (result < 0) continue; printf("\nFilter-Typ wählen:\n"); @@ -2257,16 +2112,18 @@ int menu_set_filters(){ int filter_type = safe_read_integer("Auswahl: ", 1, 2); if (filter_type < 0) continue; - strcpy(filters.annotation_filters[filters.annotation_count].pattern, pattern); + strncpy(filters.annotation_filters[filters.annotation_count].pattern, pattern, sizeof(filters.annotation_filters[filters.annotation_count].pattern) - 1); + filters.annotation_filters[filters.annotation_count].pattern[sizeof(filters.annotation_filters[filters.annotation_count].pattern) - 1] = '\0'; filters.annotation_filters[filters.annotation_count].filter_exclude_flag = (filter_type == 2) ? 1 : 0; filters.annotation_count++; printf("Annotations-Filter hinzugefügt. Gesamt: %d\n", filters.annotation_count); + } else if (choice == -2) { return -2; } else if (choice == -3) { return -3; } else if (choice != -1) { - printf("ERROR: Ungültige Auswahl! Bitte wählen Sie 1-6 oder b/m/q.\n"); + printf("ERROR: Ungültige Auswahl! Bitte wählen Sie 1-8 oder b/m/q.\n"); } } return choice; @@ -2331,7 +2188,7 @@ void menu_delete_filters(){ if (filters.annotation_flag_filter_enabled){ char* mode_str = (filters.annotation_flag_filter.filter_exclude_flag == 1) ? "ausschließen" : "einschließen"; - printf("%2d. Annotierte Einträge %s", filter_index++, mode_str); + printf("%2d. Annotierte Einträge (%s)\n", filter_index++, mode_str); } for (int i = 0; i < filters.annotation_count; i++) { @@ -2423,7 +2280,7 @@ void menu_delete_filters(){ if (filters.annotation_flag_filter_enabled) { if (current_index == choice) { filters.annotation_flag_filter_enabled = 0; - printf("Annotations-Filter gelöscht.\n"); + printf("Annotations-Flag-Filter gelöscht.\n"); return; } current_index++; @@ -2442,39 +2299,12 @@ void menu_delete_filters(){ } } -void menu_filter_mode(){ - printf("\nFILTER-MODUS ÄNDERN\n"); - printf("Aktueller Modus: %s\n\n", filters.combination_mode == 0 ? "AND (alle müssen zutreffen)" : "OR (einer muss zutreffen)"); - - printf("HINWEIS: Der Modus gilt nur für Einschluss-Filter der gleichen Kategorie.\n"); - printf("Ausschluss-Filter arbeiten immer im OR-Modus und haben Vorrang.\n\n"); - - printf("1. AND-Modus (alle Einschluss-Filter müssen zutreffen)\n"); - printf("2. OR-Modus (mindestens ein Einschluss-Filter muss zutreffen)\n"); - printf("Navigation: [b]Zurück [m]Hauptmenü [q]Beenden\n"); - printf("Auswahl: "); - - int choice = read_menu_input(); - choice = handle_menu_shortcuts(choice); - - if (choice == 1) { - filters.combination_mode = 0; - printf("Filter-Modus auf AND gesetzt.\n"); - } else if (choice == 2) { - filters.combination_mode = 1; - printf("Filter-Modus auf OR gesetzt.\n"); - } else if (choice == -2 || choice == -3) { - return; - } else if (choice != -1) { - printf("ERROR: Ungültige Auswahl! Bitte wählen Sie 1-3 oder b/m/q.\n"); - } -} - void menu_reset_filters(){ printf("\nFILTER ZURÜCKSETZEN\n"); int total_filters = filters.status_count + filters.method_count + filters.ip_count + - filters.time_count + filters.user_agent_count + filters.url_count; + filters.time_count + filters.user_agent_count + filters.url_count + + filters.annotation_flag_filter_enabled + filters.annotation_count; if (total_filters == 0) { printf("Keine Filter gesetzt zum Zurücksetzen.\n"); @@ -2497,7 +2327,6 @@ void menu_reset_filters(){ filters.time_count = 0; filters.user_agent_count = 0; filters.url_count = 0; - filters.combination_mode = 0; filters.annotation_flag_filter_enabled = 0; filters.annotation_count = 0; @@ -2524,9 +2353,8 @@ void menu_filter_management(){ printf("\nFILTER VERWALTEN\n"); printf("1. Filter hinzufügen\n"); printf("2. Filter löschen\n"); - printf("3. Filter-Modus ändern (AND/OR)\n"); - printf("4. Alle Filter zurücksetzen\n"); - printf("5. Filter-Dokumentation anzeigen\n"); + printf("3. Alle Filter zurücksetzen\n"); + printf("4. Filter-Dokumentation anzeigen\n"); printf("Navigation: [b]Zurück [m]Hauptmenü [q]Beenden\n"); printf("Auswahl: "); @@ -2539,10 +2367,8 @@ void menu_filter_management(){ } else if (choice == 2) { menu_delete_filters(); } else if (choice == 3) { - menu_filter_mode(); - } else if (choice == 4) { menu_reset_filters(); - } else if (choice == 5) { + } else if (choice == 4) { print_filter_examples(); supress_preview = 1; } else if (choice == -2) { @@ -2550,7 +2376,7 @@ void menu_filter_management(){ } else if (choice == -3) { return; } else if (choice != -1) { - printf("ERROR: Ungültige Auswahl! Bitte wählen Sie 1-5 oder b/m/q.\n"); + printf("ERROR: Ungültige Auswahl! Bitte wählen Sie 1-4 oder b/m/q.\n"); } } } @@ -2573,7 +2399,7 @@ void menu_show_entries(){ printf("2. Als CSV exportieren (Timesketch-kompatibel)\n"); printf("3. Top %d IP-Adressen anzeigen\n", TOP_X); printf("4. Top %d User-Agents anzeigen\n", TOP_X); - printf("5. Annotierte Einträge anzeigen (temporär)\n"); + printf("5. Annotierte Einträge anzeigen\n"); printf("Navigation: [b]Zurück [m]Hauptmenü [q]Beenden\n"); printf("Auswahl: "); @@ -2596,7 +2422,7 @@ void menu_show_entries(){ supress_preview = 1; show_filtered_entries(0); } else if (choice == 2) { - export_filtered_entries(0); + export_filtered_entries(NULL); } else if (choice == 3) { show_top_x_ips(); supress_preview = 1; @@ -2611,7 +2437,7 @@ void menu_show_entries(){ } else if (choice == -3) { return; } else if (choice != -1) { - printf("ERROR: Ungültige Auswahl! Bitte wählen Sie 1-4 oder b/m/q.\n"); + printf("ERROR: Ungültige Auswahl! Bitte wählen Sie 1-5 oder b/m/q.\n"); } } } @@ -2626,7 +2452,8 @@ void add_parsed_status_filter(char* value, int filter_exclude_flag) { char* endptr; int status_code = strtol(value, &endptr, 10); if (*endptr != '\0'){ - printf("ERROR: Ungültiger Wert im Statuscode-Filter: %s", value); + printf("ERROR: Ungültiger Wert im Statuscode-Filter: %s\n", value); + return; } if (status_code < 100 || status_code > 599) { printf("WARNING: Invalid status code: %s (must be 100-599)\n", value); @@ -2654,7 +2481,8 @@ void add_parsed_ip_filter(char* value, int filter_exclude_flag) { } // setzen des Filters - strcpy(filters.ip_filters[filters.ip_count].ip_address, value); + strncpy(filters.ip_filters[filters.ip_count].ip_address, value, sizeof(filters.ip_filters[filters.ip_count].ip_address) - 1); + filters.ip_filters[filters.ip_count].ip_address[sizeof(filters.ip_filters[filters.ip_count].ip_address) - 1] = '\0'; filters.ip_filters[filters.ip_count].filter_exclude_flag = filter_exclude_flag; filters.ip_count++; @@ -2673,7 +2501,8 @@ void add_parsed_method_filter(char* value, int filter_exclude_flag) { return; } - strcpy(filters.method_filters[filters.method_count].pattern, value); + strncpy(filters.method_filters[filters.method_count].pattern, value, sizeof(filters.method_filters[filters.method_count].pattern) - 1); + filters.method_filters[filters.method_count].pattern[sizeof(filters.method_filters[filters.method_count].pattern) - 1] = '\0'; filters.method_filters[filters.method_count].filter_exclude_flag = filter_exclude_flag; filters.method_count++; @@ -2692,7 +2521,8 @@ void add_parsed_useragent_filter(char* value, int filter_exclude_flag) { return; } - strcpy(filters.user_agent_filters[filters.user_agent_count].pattern, value); + strncpy(filters.user_agent_filters[filters.user_agent_count].pattern, value, sizeof(filters.user_agent_filters[filters.user_agent_count].pattern) - 1); + filters.user_agent_filters[filters.user_agent_count].pattern[sizeof(filters.user_agent_filters[filters.user_agent_count].pattern) - 1] = '\0'; filters.user_agent_filters[filters.user_agent_count].filter_exclude_flag = filter_exclude_flag; filters.user_agent_count++; @@ -2711,7 +2541,8 @@ void add_parsed_url_filter(char* value, int filter_exclude_flag) { return; } - strcpy(filters.url_filters[filters.url_count].pattern, value); + strncpy(filters.url_filters[filters.url_count].pattern, value, sizeof(filters.url_filters[filters.url_count].pattern) - 1); + filters.url_filters[filters.url_count].pattern[sizeof(filters.url_filters[filters.url_count].pattern) - 1] = '\0'; filters.url_filters[filters.url_count].filter_exclude_flag = filter_exclude_flag; filters.url_count++; @@ -2725,11 +2556,11 @@ void add_parsed_timerange_filter(char* value, int filter_exclude_flag) { return; } //lokale Kopie - struct time_filter new_time_filter = {0}; + struct time_filter_t new_time_filter = {0}; //Zur Position des :, der die Startzeit von der Endzeit trennt char* colon_pos = strchr(value, ':'); if (colon_pos == NULL) { - printf("ERROR: Missing ':' separator\n"); + printf("ERROR: Missing ':' separator in timerange filter\n"); return; } // ERsteze : durch Nullterminator. Das ist notwendig, damit strtok den Timestamp problemlos einlesen kann @@ -2890,7 +2721,8 @@ void add_parsed_annotation_filter(char* value, int filter_exclude_flag) { return; } - strcpy(filters.annotation_filters[filters.annotation_count].pattern, value); + strncpy(filters.annotation_filters[filters.annotation_count].pattern, value, sizeof(filters.annotation_filters[filters.annotation_count].pattern) - 1); + filters.annotation_filters[filters.annotation_count].pattern[sizeof(filters.annotation_filters[filters.annotation_count].pattern) - 1] = '\0'; filters.annotation_filters[filters.annotation_count].filter_exclude_flag = filter_exclude_flag; filters.annotation_count++; @@ -2959,7 +2791,6 @@ int parse_filter_argument(char* arg) { return 0; } - //int filterstr_start = arg -2; // filter-Typ parsen: 1. Länge des Strings errechnen (Position des '=' abzüglich Startposition des Strings, abzüglich 2 (für die '--' am Anfang)) int filterstr_length = equals_pos - arg -2; // 2. @@ -2986,16 +2817,6 @@ int parse_filter_argument(char* arg) { parse_filter_values(values, "annotated"); } else if (strstr(filter_type, "annotation") != NULL) { parse_filter_values(values, "annotation"); - } else if (strstr(filter_type, "mode") != NULL) { - if (strstr(values, "and") != NULL || strstr(values, "AND") != NULL) { - filters.combination_mode = 0; - if (flag_verbose) printf("DEBUG: AND-Modus gesetzt\n"); - } else if (strstr(values, "or") != NULL || strstr(values, "OR") != NULL) { - filters.combination_mode = 1; - if (flag_verbose) printf("DEBUG: OR-Modus gesetzt\n"); - } else { - printf("WARNING: ungültiger Modus-Wert: %s ('and' oder 'oder' möglich)\n", values); - } } else { printf("WARNING: Unbekannter Filtertyp: %s\n", filter_type); return 0; @@ -3004,10 +2825,9 @@ int parse_filter_argument(char* arg) { return 1; } - -// DISCLAIMER: KI-generiert +// DISCLAIMER: Updated and improved from original version void print_help(char* binary) { - printf("\nNGINX EXAMINATOR\n"); + printf("\nNGINX EXAMINATOR - Improved Version\n"); printf("Verwendung:\n"); printf(" %s -i Interaktiver Modus (Filter/Analyse/Export)\n", binary); printf(" %s -e Export generieren\n", binary); @@ -3023,9 +2843,10 @@ void print_help(char* binary) { 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(" -v Verbose-Modus für detaillierte Debug-Informationen.\n"); printf(" -h Zeigt diese Hilfe an.\n"); - printf("\nKOMMNDOZEILEN-FILTER (nur mit -f Flag):\n"); + printf("\nKOMMANDOZEILEN-FILTER (nur mit -f Flag):\n"); printf(" --status=CODE[,CODE...] HTTP-Status-Codes (z.B. 200,404,500)\n"); printf(" --ip=ADRESSE[,ADRESSE...] IP-Adressen (exakte Übereinstimmung)\n"); printf(" --method=METHODE[,METHODE...] HTTP-Methoden (z.B. GET,POST,ATYPICAL)\n"); @@ -3034,7 +2855,6 @@ void print_help(char* binary) { printf(" --annotated=true Einträge mit/ohne Annotationen (true oder !true)\n"); 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(" --mode=MODE Filtermodus: 'and' oder 'or' (Standard: and)\n"); printf("\nFILTER-SYNTAX:\n"); printf(" Einschluss: Wert (nur Einträge MIT diesem Wert)\n"); @@ -3048,11 +2868,11 @@ void print_help(char* binary) { 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("\nFILTER-LOGIK:\n"); + printf("\nFILTER-LOGIK (Vereinfacht):\n"); printf(" - Ausschluss-Filter (!) haben IMMER Vorrang vor Einschluss-Filtern\n"); - printf(" - AND-Modus: ALLE Einschluss-Filter pro Kategorie müssen zutreffen\n"); - printf(" - OR-Modus: MINDESTENS EIN Einschluss-Filter pro Kategorie muss zutreffen\n"); - printf(" - Ausschluss-Filter arbeiten kategorie-intern immer im OR-Modus\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("\nUnterstützte Eingaben:\n"); printf(" - Normale NGINX-Access-Logs: *.log\n"); @@ -3071,14 +2891,18 @@ void print_help(char* binary) { printf(" Spalten: datetime, message timestamp_desc, ip_address, method, url_path, status_code,\n"); printf(" bytes_sent, user_agent, source_file, parsing_timestamp, tag\n"); - printf("\nAnnotationen:\n"); - printf(" - Automatische Erkennung verdächtiger Muster (z.B. 'Long Payload')\n"); + printf("\nAnnotationen (Automatische Erkennung):\n"); + printf(" - Automatische Erkennung verdächtiger Muster (z.B. 'Long Payload', 'SQL Injection')\n"); 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 & Einschränkungen:\n"); + printf("\nHinweise & Verbesserungen:\n"); printf(" - Parser erwartet das obige Standardformat. Abweichungen können zum Abbruch führen.\n"); - printf(" - Zeitzone aus dem Log wird gelesen, aber intern als einfache Felder verarbeitet.\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"); } @@ -3088,67 +2912,113 @@ int main(int argc, char* argv[]) { return 1; } - printf("\nNGINX EXAMINATOR\n"); + printf("\nNGINX EXAMINATOR - Advanced Log Analysis Tool\n"); - // Dateipfad: wenn nicht angegeben, wird /var/log/nginx (Standardpfad) untersucht - char* input_path; - int arg_offset = 1; // Offset für die args + // Hybrid argument parsing: support both path-first and flag-first approaches + char* input_path = "/var/log/nginx"; // Default path + int arg_offset = 1; - // Prüfen des ersten Zeichens des ersten Arguments - Pfad oder Flag? - if (argv[1][0] == '-') { - // Ist ein Flag - Standardpfad, Offset bleibt bei 1 - input_path = "/var/log/nginx/"; - arg_offset = 1; - } else { + // Check if first argument is a path (doesn't start with -) + if (argv[1][0] != '-') { input_path = argv[1]; - arg_offset = 2; // Offset inkrementieren, Dateipfad schiebt die args nach hinten + arg_offset = 2; // Start parsing flags from argv[2] + if (flag_verbose) printf("DEBUG: Using path from first argument: %s\n", input_path); + } else { + arg_offset = 1; // Start parsing flags from argv[1] + if (flag_verbose) printf("DEBUG: Using default path: %s\n", input_path); } - + // Flag variables int flag_interactive = 0; int flag_export = 0; int flag_help = 0; - // int flag_verbose = 0; - global definiert - + int flag_filter = 0; char export_filename[90]; int flag_has_filename = 0; allocate_initial_memory(); - if (argc >= 2){ - // hier wird das offset angewendet - for (int i=arg_offset; i 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]); + cleanup_memory(); return 1; - }else if (flag_interactive == 1) { - // entweder Standardpfad oder übergebener Pfad + } + + // Execute based on selected mode + if (flag_interactive == 1) { + // Interactive mode load_log_file(input_path); if (total_entries == 0) { @@ -3156,8 +3026,8 @@ int main(int argc, char* argv[]) { cleanup_memory(); return 1; } + int choice = 0; - // Standardnavigation aus read_menu_input while (choice != 4 && choice != -4) { show_status(); show_main_menu(); @@ -3175,7 +3045,7 @@ int main(int argc, char* argv[]) { } else if (choice == 2) { menu_show_entries(); } else if (choice == 3) { - export_filtered_entries(0); + export_filtered_entries(NULL); } else if (choice == 4) { if (flag_verbose) printf("DEBUG: Programmende\n"); break; @@ -3185,17 +3055,43 @@ int main(int argc, char* argv[]) { printf("ERROR: Ungültige Auswahl! Bitte wählen Sie 1-4 oder b/m/q.\n"); } } - } else if (flag_export == 1){ + + } else if (flag_export == 1) { + // Export mode load_log_file(input_path); if (flag_has_filename == 1) { export_filtered_entries(export_filename); } else { export_filtered_entries(NULL); } - } else if (flag_help == 1){ - print_help(argv[0]); - return 1; - } + + } else if (flag_filter == 1) { + // Filter mode - apply command line filters and show summary + load_log_file(input_path); + int filtered_count = count_filtered_entries(); + printf("\nFILTER-ERGEBNISSE:\n"); + printf("Geladen: %d Einträge\n", total_entries); + printf("Gefiltert: %d Einträge entsprechen den Kriterien\n", filtered_count); + printf("Verdächtige Muster: %d automatisch erkannt\n\n", suspicious_patterns_count); + + if (filtered_count > 0) { + printf("Verwenden Sie -e für CSV-Export oder -i für interaktive Analyse.\n"); + } + + } else { + // Default mode - simple summary + load_log_file(input_path); + int filtered_count = count_filtered_entries(); + printf("\nZUSAMMENFASSUNG:\n"); + printf("Geladen: %d Einträge aus %s\n", total_entries, input_path); + printf("Verdächtige Muster automatisch erkannt: %d\n", suspicious_patterns_count); + printf("\nVerfügbare Modi:\n"); + printf(" -i Interaktiver Modus für detaillierte Analyse\n"); + printf(" -e CSV-Export für Timesketch\n"); + printf(" -f Kommandozeilen-Filter anwenden\n"); + printf(" -h Vollständige Hilfe anzeigen\n"); + } + cleanup_memory(); return 0; } \ No newline at end of file