This commit is contained in:
overcuriousity 2025-08-30 22:06:32 +02:00
parent 76b8da4685
commit bf2249fa7b

View File

@ -20,7 +20,7 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
#define MAX_LINE_LENGTH_BUF 2048 #define MAX_LINE_LENGTH_BUF 2048
#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 expand_memory_if_needed() 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_URL_PATH_LENGTH 512
@ -89,12 +89,12 @@ int max_entries = 0;
int total_entries = 0; int total_entries = 0;
struct filter_system filters = {0}; struct filter_system filters = {0};
// Helper für die Erkennung von Leerzeichen // Hilfsfunktion für die Erkennung von Leerzeichen
int is_space(char c) { int is_space(char c) {
return (c == ' ' || c == '\t'); return (c == ' ' || c == '\t');
} }
// Helper zum Überspringen von Leerzeichen, gibt den Pointer für das nächste nicht-Leerzeichen zurück. Nötig für strtok-Parser. // Hilfsfunktion zum Überspringen von Leerzeichen, gibt den Pointer für das nächste nicht-Leerzeichen zurück. Nötig für Parser.
char* skip_spaces(char* str) { char* skip_spaces(char* str) {
while (is_space(*str)) { while (is_space(*str)) {
str++; str++;
@ -102,7 +102,7 @@ char* skip_spaces(char* str) {
return str; return str;
} }
// TODO // Kopiert einen Eingabestring von einem Quellbereich zu einem Zielbereich, bis ein Leerzeichen (oder Nullterminator) erreicht wird
void copy_until_space(char* destination, char* source, int max_length) { void copy_until_space(char* destination, char* source, int max_length) {
int i = 0; int i = 0;
while (source[i] != ' ' && source[i] != '\0' && i < max_length - 1) { while (source[i] != ' ' && source[i] != '\0' && i < max_length - 1) {
@ -129,38 +129,49 @@ int month_name_to_number(char* month_name) {
return 1; return 1;
} }
int compare_times(struct simple_time time1, struct simple_time time2) { // Vergleich von Zeitstempel-Strukturen - time_checkvalue ist der Prüfwert, time_filtervalue ist der Vergleichswert (Filter) - Funktion wird in time_matches() aufgerufen
if (time1.year != time2.year) return (time1.year < time2.year) ? -1 : 1; int compare_times(struct simple_time time_checkvalue, struct simple_time time_filtervalue) {
if (time1.month != time2.month) return (time1.month < time2.month) ? -1 : 1; /*
if (time1.day != time2.day) return (time1.day < time2.day) ? -1 : 1; Rückgabewert 1 -> Prüfwert ist NACH dem Vergleichswert
if (time1.hour != time2.hour) return (time1.hour < time2.hour) ? -1 : 1; Es wird erst das Jahr geprüft, dann der Monat u.s.w.
if (time1.minute != time2.minute) return (time1.minute < time2.minute) ? -1 : 1; Ist ein Wert gleich, wird der nächstfeinere Wert geprüft.
if (time1.second != time2.second) return (time1.second < time2.second) ? -1 : 1; Sobald die Werte sich unterscheiden, findet die Prüfung statt und ein Rückgabewert wird ausgegeben.
Gibt 0 zurück, wenn der Wert dem Filter exakt gleicht
*/
if (time_checkvalue.year != time_filtervalue.year) return (time_checkvalue.year < time_filtervalue.year) ? -1 : 1;
if (time_checkvalue.month != time_filtervalue.month) return (time_checkvalue.month < time_filtervalue.month) ? -1 : 1;
if (time_checkvalue.day != time_filtervalue.day) return (time_checkvalue.day < time_filtervalue.day) ? -1 : 1;
if (time_checkvalue.hour != time_filtervalue.hour) return (time_checkvalue.hour < time_filtervalue.hour) ? -1 : 1;
if (time_checkvalue.minute != time_filtervalue.minute) return (time_checkvalue.minute < time_filtervalue.minute) ? -1 : 1;
if (time_checkvalue.second != time_filtervalue.second) return (time_checkvalue.second < time_filtervalue.second) ? -1 : 1;
return 0; return 0;
} }
// Standardfunktion zum Leeren des Input-Buffers
void clear_input_buffer() { void clear_input_buffer() {
int c; int c;
while ((c = getchar()) != '\n' && c != EOF) { while ((c = getchar()) != '\n' && c != EOF) {
} }
} }
// Hilfsfunktion zum Prüfen, ob die Zahl eine Dezimalzahl ist. Wird im Menu verwendet, um die Nutzereingabe zu prüfen
int read_safe_integer() { int read_safe_integer() {
int number; int number;
int result = scanf("%d", &number); int result = scanf("%d", &number);
if (result != 1) { if (result != 1) {
clear_input_buffer(); clear_input_buffer();
return -1; return -1; // Fehler, wenn die Nutzereingabe kein Integer ist
} }
clear_input_buffer(); clear_input_buffer();
return number; return number;
} }
// Speicher freigeben und mit 0 überschreiben (Prävention von use-after-free-Schwachstelle)
void cleanup_memory() { void cleanup_memory() {
if (all_entries != NULL) { if (all_entries != NULL) {
printf("Gebe %zu Bytes Speicher frei...\n", max_entries * sizeof(struct log_entry)); printf("%lu Bytes Speicher werden freigegeben\n", (unsigned long)(max_entries * sizeof(struct log_entry)));
free(all_entries); free(all_entries);
all_entries = NULL; all_entries = NULL;
} }
@ -168,47 +179,49 @@ void cleanup_memory() {
total_entries = 0; total_entries = 0;
} }
// sauberes Schließen und bereinigen bei Fehlerstatus, sofern Speicher nicht alloziert werden kann
void cleanup_and_exit() { void cleanup_and_exit() {
printf("Programm wird wegen Speicherfehler beendet...\n"); printf("Speicherfehler, Programmende\n");
cleanup_memory(); cleanup_memory();
exit(1); exit(1);
} }
void expand_memory_if_needed() { // Erweiterung des Speichers für dynamische Speicherallokation
void mem_expand_dynamically() {
// total_entries werden beim parsen am Anfang gezählt (load_regular_file()), max_entries werden initial festgelegt
if (total_entries >= max_entries) { if (total_entries >= max_entries) {
int old_max = max_entries; int old_max = max_entries;
max_entries = max_entries * GROWTH_FACTOR; max_entries = max_entries * GROWTH_FACTOR;
printf("Speicher wird erweitert: %d -> %d Einträge\n", old_max, max_entries); printf("Dynamische Speichererweiterung von %d auf %d Einträge um Faktor %d\n", old_max, max_entries, GROWTH_FACTOR);
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("KRITISCHER FEHLER: Speicher konnte nicht auf %d Einträge erweitert werden!\n", max_entries); printf("ERROR: Speicher konnte nicht auf %d Einträge erweitert werden, Programm wird beendet...\n", max_entries);
printf("Benötigter Speicher: %zu Bytes\n", 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();
} }
all_entries = new_ptr; all_entries = new_ptr;
printf("Speicher erfolgreich erweitert auf %zu Bytes\n", printf("Speicher erfolgreich erweitert auf %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry)));
max_entries * sizeof(struct log_entry));
} }
} }
void allocate_initial_memory() { void allocate_initial_memory() {
max_entries = INITIAL_ENTRIES; 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));
if (all_entries == NULL) { if (all_entries == NULL) {
printf("KRITISCHER FEHLER: Konnte %d Einträge nicht allokieren!\n", max_entries); printf("ERROR: Konnte %d Einträge nicht allozieren, Programm wird beendet...\n", max_entries);
printf("Benötigter Speicher: %zu Bytes\n", max_entries * sizeof(struct log_entry)); printf("ERROR: %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry)));
exit(1); exit(1); // cleanup_and_exit() nicht nötig, da der Speicherbereich nicht beschrieben wurde - use-after-free unproblematisch
} }
printf("Speicher allokiert für %d Log-Einträge (%zu Bytes)\n", printf("Speicher erfolgreich alloziert für %d Log-Einträge (%lu Bytes)\n", max_entries, (unsigned long)(max_entries * sizeof(struct log_entry)));
max_entries, max_entries * sizeof(struct log_entry));
} }
// Hilfsfunktion zum Prüfen, ob es sich beim Pfad um ein Directory handelt - für rekursives Parsen
int is_directory(char* path) { int is_directory(char* path) {
struct stat path_stat; struct stat path_stat;
if (stat(path, &path_stat) != 0) { if (stat(path, &path_stat) != 0) {
@ -217,28 +230,34 @@ int is_directory(char* path) {
return S_ISDIR(path_stat.st_mode); return S_ISDIR(path_stat.st_mode);
} }
// Hilfsfunktion zum prüfen, ob es sich um eine plausible nginx-Logdatei handelt (Metrik: Dateiname - BESSER: Regex oder Magic Bytes?)
int is_log_file(char* filename) { int is_log_file(char* filename) {
char* log_pos = strstr(filename, ".log"); char* log_pos = strstr(filename, ".log"); // Sucht und findet den Pointer auf die Startposition des Suchstrings
if (log_pos == NULL) return 0; if (log_pos == NULL) return 0;
char* after_log = log_pos + 4; char* after_log = log_pos + 4;
if (*after_log == '\0') return 1; if (*after_log == '\0') return 1; // true, wenn die Datei mit .log endet
if (*after_log == '.') { if (*after_log == '.') {
after_log++; after_log++;
if (*after_log == '\0') return 0; if (*after_log == '\0') return 0; // false, wenn die Datei mit einem Punkt nach .log endet
while (*after_log != '\0') { while (*after_log != '\0') {
if (*after_log < '0' || *after_log > '9') return 0; if (*after_log < '0' || *after_log > '9') return 0; // wenn was anderes als Zahlen nach dem . kommen, gib False zurück
after_log++; after_log++;
} }
return 1; return 1; // true, wenn nach .log. noch Zahlen vorhanden sind, wie typisch bei NGINX-Logrotation
} }
return 0; return 0; // false, wenn nichts zutrifft
} }
int parse_simple_log_line(char* line, int entry_index) { /*
Parser. Regex Parser und strtok haben sich als schwieriger herausgestellt, da nginx Leerzeichen-getrennte Werte in den Logs hat, aber auch Leerzeichen innerhalb der Werte vorkommen.
Daher ist das Parsing am einfachsten, wenn ein Pointer-basierter Algorithmus die Zeile Stück für Stück einliest und die Erwartungswerte in die struct schreibt.
Fehleranfällig, wenn das Logformat nicht dem Standard entspricht - das gilt aber auch für andere Parser.
*/
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
char* current_pos = line; char* current_pos = line;
current_pos = skip_spaces(current_pos); current_pos = skip_spaces(current_pos);
@ -360,7 +379,7 @@ void load_regular_file(char* filename) {
int loaded_from_this_file = 0; int loaded_from_this_file = 0;
while (fgets(line, sizeof(line), file) != NULL) { while (fgets(line, sizeof(line), file) != NULL) {
expand_memory_if_needed(); mem_expand_dynamically();
if (parse_simple_log_line(line, total_entries)) { if (parse_simple_log_line(line, total_entries)) {
total_entries++; total_entries++;
@ -431,8 +450,7 @@ void load_log_file(char* path) {
} }
printf("Erfolgreich %d Einträge insgesamt geladen.\n", total_entries); printf("Erfolgreich %d Einträge insgesamt geladen.\n", total_entries);
printf("Aktueller Speicherverbrauch: %zu Bytes für %d Einträge\n", printf("Aktueller Speicherverbrauch: %lu Bytes für %d Einträge\n", (unsigned long)(max_entries * sizeof(struct log_entry)), max_entries);
max_entries * sizeof(struct log_entry), max_entries);
} }
int status_code_matches(int status_code) { int status_code_matches(int status_code) {
@ -487,35 +505,39 @@ int ip_address_matches(char* ip_address) {
return 1; return 1;
} }
// 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 entry_time) {
// gibt 1 zurück, wenn kein Filter gesetzt wird -> der komplette Log-Datensatz wird ausgegeben
if (filters.time_count == 0) return 1; if (filters.time_count == 0) return 1;
int has_include_filters = 0; int has_include_filters = 0;
int include_match = 0; int include_match = 0;
// es können mehrere Filter gleichzeitig gesetzt sein, diese werden alle nacheinander in einer Schleife geprüft
for (int i = 0; i < filters.time_count; i++) { for (int i = 0; i < filters.time_count; i++) {
int in_range = (compare_times(entry_time, filters.time_filters[i].start_time) >= 0 && 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); compare_times(entry_time, filters.time_filters[i].end_time) <= 0); // gibt 0=false zurück, wenn der Prüfwert nicht im Filterbereich ist
if (filters.time_filters[i].mode == FILTER_INCLUDE) { if (filters.time_filters[i].mode == FILTER_INCLUDE) {
has_include_filters = 1; has_include_filters = 1; // Flag für inlusive Filter
if (in_range) { if (in_range) {
include_match = 1; include_match = 1; // gibt 1=true aus, wenn der Prüfwert in den geprüften inklusiven Filter passt
} }
// ausschließen-Filter
} else { } else {
if (in_range) { if (in_range) {
return 0; return 0; // gibt 0=false aus, wenn der geprüfte Filter exklusiv ist, und der Prüfwert in den Filterbereich passt
} }
} }
} }
if (has_include_filters) { if (has_include_filters) {
return include_match; return include_match; // gibt 0=false aus, wenn es inklusive Filter gibt (has_include_filters =1), aber kein Zeitstempel in den Filterbereich passt
} }
return 1; return 1;
} }
// Prüfen aller Filter im AND-Modus oder OR-Modus (combination_mode) pro Log-Eintrag
int passes_filter(int entry_index) { 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);
@ -531,6 +553,7 @@ int passes_filter(int entry_index) {
} }
} }
// Einfacher Zähler für alle Einträge, die die Filterfunktionen bestehen
int count_filtered_entries() { int count_filtered_entries() {
int count = 0; int count = 0;
for (int i = 0; i < total_entries; i++) { for (int i = 0; i < total_entries; i++) {
@ -546,8 +569,7 @@ void show_status() {
if (total_entries > 0) { if (total_entries > 0) {
printf("✅ Log-Daten: %d Einträge geladen\n", total_entries); printf("✅ Log-Daten: %d Einträge geladen\n", total_entries);
printf(" Speicherverbrauch: %zu Bytes (%d Einträge Kapazität)\n", printf(" Speicherverbrauch: %lu Bytes (%d Einträge Kapazität)\n", (unsigned long)(max_entries * sizeof(struct log_entry)), max_entries);
max_entries * sizeof(struct log_entry), max_entries);
} else { } else {
printf("❌ Keine Log-Daten geladen\n"); printf("❌ Keine Log-Daten geladen\n");
} }