src/main.c aktualisiert

This commit is contained in:
Mario Stöckl 2025-09-01 13:08:05 +00:00
parent 65e82f45b7
commit 3d11d2f0f2

View File

@ -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 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 GROWTH_FACTOR 2 // wird in mem_expand_dynamically() genutzt, um den Speicher zu vergrößern
#define MAX_FILTERS 100 #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. // definiert Variablen für den Filtermodus. FILTER_INCLUDE=0, FILTER_EXCLUDE=1. Verbessert die Lesbarkeit des Codes.
typedef enum { typedef enum {
@ -44,10 +44,12 @@ struct simple_time {
struct log_entry { struct log_entry {
char ip_address[50]; // ausreichende Längenbegrenzung für IP-Adressen. Könnte theoretisch auch ipv6 (ungetestet) 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 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 status_code;
int bytes_sent; int bytes_sent;
struct simple_time time; struct simple_time time;
char referrer[128];
char user_agent[256];
}; };
// Struktur für einen Status-Filtereintrag mit Inhalt & Modus // Struktur für einen Status-Filtereintrag mit Inhalt & Modus
@ -69,6 +71,12 @@ struct time_filter {
filter_mode_t mode; 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. // Struktur zum erhalten aller Filtereinträge, kann im Dialogbetrieb bearbeitet werden. Mit Zähler.
struct filter_system { struct filter_system {
struct status_filter status_filters[MAX_FILTERS]; struct status_filter status_filters[MAX_FILTERS];
@ -80,6 +88,9 @@ struct filter_system {
struct time_filter time_filters[MAX_FILTERS]; struct time_filter time_filters[MAX_FILTERS];
int time_count; 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 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)); struct log_entry *new_ptr = realloc(all_entries, max_entries * sizeof(struct log_entry));
if (new_ptr == NULL) { 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))); printf("ERROR: Benötigter Speicher: %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry)));
cleanup_and_exit(); cleanup_and_exit();
} }
@ -213,7 +224,7 @@ void allocate_initial_memory() {
all_entries = malloc(max_entries * sizeof(struct log_entry)); all_entries = malloc(max_entries * sizeof(struct log_entry));
if (all_entries == NULL) { 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))); 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 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. Fehleranfällig, wenn das Logformat nicht dem Standard entspricht - das gilt aber auch für andere Parser.
*/ */
// Standard-nginx-accesslog: // 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);" "-" // 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 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; char* current_pos = line;
// leere Zeichen am Anfang überspringen // leere Zeichen am Anfang überspringen
current_pos = skip_spaces(current_pos); 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 // mehrmaliges Überspringen des Wertes, der nicht von Interesse ist, Überspringen des Leerzeichens
while (*current_pos != ' ' && *current_pos != '\0') current_pos++; while (*current_pos != ' ' && *current_pos != '\0') current_pos++;
current_pos = skip_spaces(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++; while (*current_pos != ' ' && *current_pos != '\0') current_pos++;
current_pos = skip_spaces(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);" "-" // 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);" "-" // 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 { } 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(); cleanup_and_exit();
} }
current_pos = skip_spaces(current_pos); current_pos = skip_spaces(current_pos);
// Weiter mit dem String innerhalb "", aus dem die HTTP-Methode und der URL-Pfad zu entnehmen ist // 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 == '"') { if (*current_pos == '"') {
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);" "-" // 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++; // First, try to read what should be the HTTP method
if (*current_pos == '"') current_pos++; 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 { } 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(); 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); current_pos = skip_spaces(current_pos);
// genauso mit bytegröße der Anfrage // 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; all_entries[entry_index].bytes_sent = 0;
while (*current_pos >= '0' && *current_pos <= '9') { while (*current_pos >= '0' && *current_pos <= '9') {
all_entries[entry_index].bytes_sent = all_entries[entry_index].bytes_sent * 10 + (*current_pos - '0'); all_entries[entry_index].bytes_sent = all_entries[entry_index].bytes_sent * 10 + (*current_pos - '0');
current_pos++; 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; 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); 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) { int status_code_matches(int status_code) {
if (filters.status_count == 0) return 1; 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 status_match = status_code_matches(all_entries[entry_index].status_code);
int ip_match = ip_address_matches(all_entries[entry_index].ip_address); int ip_match = ip_address_matches(all_entries[entry_index].ip_address);
int time_match = time_matches(all_entries[entry_index].time); 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) { if (filters.combination_mode == 0) {
return status_match && ip_match && time_match; return status_match && ip_match && time_match && user_agent_match;
} else { } 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; 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"); 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) { if (total_filters == 0) {
printf(" Keine Filter gesetzt\n"); printf(" Keine Filter gesetzt\n");
printf(" Filter-Modus: %s\n", filters.combination_mode == 0 ? "AND" : "OR");
} else { } 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) { if (filters.status_count > 0) {
printf(" Status-Codes (%d): ", filters.status_count); printf(" Status-Codes (%d): ", filters.status_count);
@ -633,6 +801,16 @@ void show_status() {
printf("\n"); 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) { if (filters.time_count > 0) {
printf(" Zeiträume (%d):\n", filters.time_count); printf(" Zeiträume (%d):\n", filters.time_count);
for (int i = 0; i < filters.time_count; i++) { for (int i = 0; i < filters.time_count; i++) {
@ -704,11 +882,11 @@ void export_filtered_entries() {
FILE* file = fopen(filename, "w"); FILE* file = fopen(filename, "w");
if (file == NULL) { if (file == NULL) {
printf("FEHLER: Kann Datei '%s' nicht erstellen!\n", filename); printf("ERROR: Kann Datei '%s' nicht erstellen!\n", filename);
return; 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; int exported_count = 0;
char iso_datetime[32]; char iso_datetime[32];
@ -726,7 +904,8 @@ void export_filtered_entries() {
all_entries[i].request_method, all_entries[i].request_method,
all_entries[i].url_path, all_entries[i].url_path,
all_entries[i].status_code, 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", fprintf(file, "\"%s\",\"%s\",\"HTTP Access Log\",%lld,\"%s\",\"%s\",\"%s\",%d,%d\n",
message_text, message_text,
@ -736,14 +915,15 @@ void export_filtered_entries() {
all_entries[i].request_method, all_entries[i].request_method,
all_entries[i].url_path, all_entries[i].url_path,
all_entries[i].status_code, all_entries[i].status_code,
all_entries[i].bytes_sent); all_entries[i].bytes_sent,
all_entries[i].user_agent);
exported_count++; exported_count++;
} }
} }
fclose(file); 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 { 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() { void show_filtered_entries() {
int shown_count = 0; int shown_count = 0;
printf("\n=== GEFILTERTE LOG-EINTRÄGE ===\n"); printf("\n=== GEFILTERTE LOG-EINTRÄGE ===\n");
printf("IP-Adresse | Methode | URL | Status | Bytes | Zeit\n"); printf("IP-Adresse | Methode | URL | Status | Bytes | User Agent | Zeit\n");
printf("-----------------|---------|------------------------|--------|-------|------------------\n"); printf("-----------------|---------|------------------------|--------|-------|--------------------------------------|------------------\n");
for (int i = 0; i < total_entries; i++) { for (int i = 0; i < total_entries; i++) {
if (passes_filter(i)) { if (passes_filter(i)) {
@ -820,6 +1060,7 @@ void show_filtered_entries() {
all_entries[i].url_path, all_entries[i].url_path,
all_entries[i].status_code, all_entries[i].status_code,
all_entries[i].bytes_sent, all_entries[i].bytes_sent,
all_entries[i].user_agent,
all_entries[i].time.day, all_entries[i].time.day,
all_entries[i].time.month, all_entries[i].time.month,
all_entries[i].time.year, all_entries[i].time.year,
@ -874,10 +1115,11 @@ void menu_set_filters() {
while (choice != 4) { while (choice != 4) {
show_status(); show_status();
printf("\n=== FILTER SETZEN ===\n"); printf("\n=== FILTER SETZEN ===\n");
printf("1. Status-Code Filter hinzufügen\n"); printf("1. Status-Code Filter hinzufügen (exakte Suche)\n");
printf("2. IP-Adresse Filter hinzufügen\n"); printf("2. IP-Adresse Filter hinzufügen (exakte Suche)\n");
printf("3. Zeitraum Filter hinzufügen\n"); printf("3. Zeitraum Filter hinzufügen (interaktiv)\n");
printf("4. Zurück zum Filter-Menü\n"); printf("4. User-Agent-Filter setzen (Freitext)\n");
printf("5. Zurück zum Filter-Menü\n");
printf("Auswahl: "); printf("Auswahl: ");
choice = read_safe_integer(); choice = read_safe_integer();
@ -1049,15 +1291,43 @@ void menu_set_filters() {
printf("✅ Zeitraum-Filter hinzugefügt. Total: %d\n", filters.time_count); printf("✅ Zeitraum-Filter hinzugefügt. Total: %d\n", filters.time_count);
} else if (choice == 4) { } 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; return;
} else if (choice != -1) { } 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() { 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) { if (total_filters == 0) {
printf("Keine Filter gesetzt zum Löschen.\n"); 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); 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++) { for (int i = 0; i < filters.time_count; i++) {
char* mode_str = (filters.time_filters[i].mode == FILTER_EXCLUDE) ? "(ausschließen)" : "(einschließen)"; 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", 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++; 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++) { for (int i = 0; i < filters.time_count; i++) {
if (current_index == choice) { if (current_index == choice) {
for (int j = i; j < filters.time_count - 1; j++) { for (int j = i; j < filters.time_count - 1; j++) {
@ -1173,6 +1460,7 @@ void menu_reset_filters() {
filters.status_count = 0; filters.status_count = 0;
filters.ip_count = 0; filters.ip_count = 0;
filters.time_count = 0; filters.time_count = 0;
filters.user_agent_count = 0;
filters.combination_mode = 0; filters.combination_mode = 0;
printf("✅ Alle Filter zurückgesetzt.\n"); printf("✅ Alle Filter zurückgesetzt.\n");
@ -1215,10 +1503,11 @@ void menu_show_entries() {
printf("\n=== EINTRÄGE ANZEIGEN ===\n"); printf("\n=== EINTRÄGE ANZEIGEN ===\n");
int filtered_count = count_filtered_entries(); int filtered_count = count_filtered_entries();
printf("Aktuell %d gefilterte Einträge verfügbar.\n", filtered_count); printf("Aktuell %d gefilterte Einträge verfügbar.\n", filtered_count);
printf("1. Gefilterte Einträge vollständig anzeigen\n"); printf("1. Vollständig anzeigen\n");
printf("2. Gefilterte Einträge in Datei exportieren\n"); printf("2. Zu .csv exportieren (Timesketch-kompatibel)\n");
printf("3. Top 10 IP-Adressen anzeigen\n"); printf("3. Top 10 IP-Adressen\n");
printf("4. Zurück zum Hauptmenü\n"); printf("4. Top 10 User-Agents\n");
printf("5. Zurück zum Hauptmenü\n");
printf("Auswahl: "); printf("Auswahl: ");
choice = read_safe_integer(); choice = read_safe_integer();
@ -1239,6 +1528,8 @@ void menu_show_entries() {
} else if (choice == 3) { } else if (choice == 3) {
show_top_10_ips(); show_top_10_ips();
} else if (choice == 4) { } else if (choice == 4) {
show_top_user_agents();
} else if (choice == 5) {
return; return;
} else if (choice != -1) { } else if (choice != -1) {
printf("Ungültige Auswahl! Bitte wählen Sie 1-4.\n"); printf("Ungültige Auswahl! Bitte wählen Sie 1-4.\n");
@ -1249,7 +1540,7 @@ void menu_show_entries() {
void show_main_menu() { void show_main_menu() {
printf("\nHauptmenü:\n"); printf("\nHauptmenü:\n");
printf("1. Filter verwalten\n"); printf("1. Filter verwalten\n");
printf("2. Gefilterte Einträge anzeigen\n"); printf("2. Anzeigemodi\n");
printf("3. Statistiken anzeigen\n"); printf("3. Statistiken anzeigen\n");
printf("4. Beenden\n"); printf("4. Beenden\n");
printf("Auswahl: "); printf("Auswahl: ");