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 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_URL_PATH_LENGTH 512
@ -89,12 +89,12 @@ int max_entries = 0;
int total_entries = 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) {
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) {
while (is_space(*str)) {
str++;
@ -102,7 +102,7 @@ char* skip_spaces(char* 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) {
int i = 0;
while (source[i] != ' ' && source[i] != '\0' && i < max_length - 1) {
@ -129,38 +129,49 @@ int month_name_to_number(char* month_name) {
return 1;
}
int compare_times(struct simple_time time1, struct simple_time time2) {
if (time1.year != time2.year) return (time1.year < time2.year) ? -1 : 1;
if (time1.month != time2.month) return (time1.month < time2.month) ? -1 : 1;
if (time1.day != time2.day) return (time1.day < time2.day) ? -1 : 1;
if (time1.hour != time2.hour) return (time1.hour < time2.hour) ? -1 : 1;
if (time1.minute != time2.minute) return (time1.minute < time2.minute) ? -1 : 1;
if (time1.second != time2.second) return (time1.second < time2.second) ? -1 : 1;
// 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) {
/*
Rückgabewert 1 -> Prüfwert ist NACH dem Vergleichswert
Es wird erst das Jahr geprüft, dann der Monat u.s.w.
Ist ein Wert gleich, wird der nächstfeinere Wert geprüft.
Sobald die Werte sich unterscheiden, findet die Prüfung statt und ein Rückgabewert wird ausgegeben.
Gibt 0 zurück, wenn der Wert dem Filter exakt gleicht
*/
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;
}
// Standardfunktion zum Leeren des Input-Buffers
void clear_input_buffer() {
int c;
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 number;
int result = scanf("%d", &number);
if (result != 1) {
clear_input_buffer();
return -1;
return -1; // Fehler, wenn die Nutzereingabe kein Integer ist
}
clear_input_buffer();
return number;
}
// Speicher freigeben und mit 0 überschreiben (Prävention von use-after-free-Schwachstelle)
void cleanup_memory() {
if (all_entries != NULL) {
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);
all_entries = NULL;
}
@ -168,47 +179,49 @@ void cleanup_memory() {
total_entries = 0;
}
// sauberes Schließen und bereinigen bei Fehlerstatus, sofern Speicher nicht alloziert werden kann
void cleanup_and_exit() {
printf("Programm wird wegen Speicherfehler beendet...\n");
printf("Speicherfehler, Programmende\n");
cleanup_memory();
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) {
int old_max = max_entries;
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));
if (new_ptr == NULL) {
printf("KRITISCHER FEHLER: Speicher konnte nicht auf %d Einträge erweitert werden!\n", max_entries);
printf("Benötigter Speicher: %zu Bytes\n", max_entries * sizeof(struct log_entry));
printf("ERROR: Speicher konnte nicht auf %d Einträge erweitert werden, Programm wird beendet...\n", max_entries);
printf("ERROR: Benötigter Speicher: %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry)));
cleanup_and_exit();
}
all_entries = new_ptr;
printf("Speicher erfolgreich erweitert auf %zu Bytes\n",
max_entries * sizeof(struct log_entry));
printf("Speicher erfolgreich erweitert auf %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry)));
}
}
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));
if (all_entries == NULL) {
printf("KRITISCHER FEHLER: Konnte %d Einträge nicht allokieren!\n", max_entries);
printf("Benötigter Speicher: %zu Bytes\n", max_entries * sizeof(struct log_entry));
exit(1);
printf("ERROR: Konnte %d Einträge nicht allozieren, Programm wird beendet...\n", max_entries);
printf("ERROR: %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry)));
exit(1); // cleanup_and_exit() nicht nötig, da der Speicherbereich nicht beschrieben wurde - use-after-free unproblematisch
}
printf("Speicher allokiert für %d Log-Einträge (%zu Bytes)\n",
max_entries, max_entries * sizeof(struct log_entry));
printf("Speicher erfolgreich alloziert für %d Log-Einträge (%lu Bytes)\n", max_entries, (unsigned long)(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) {
struct stat path_stat;
if (stat(path, &path_stat) != 0) {
@ -217,28 +230,34 @@ int is_directory(char* path) {
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) {
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;
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 == '.') {
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') {
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++;
}
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;
current_pos = skip_spaces(current_pos);
@ -360,7 +379,7 @@ void load_regular_file(char* filename) {
int loaded_from_this_file = 0;
while (fgets(line, sizeof(line), file) != NULL) {
expand_memory_if_needed();
mem_expand_dynamically();
if (parse_simple_log_line(line, 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("Aktueller Speicherverbrauch: %zu Bytes für %d Einträge\n",
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);
}
int status_code_matches(int status_code) {
@ -487,35 +505,39 @@ int ip_address_matches(char* ip_address) {
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) {
// gibt 1 zurück, wenn kein Filter gesetzt wird -> der komplette Log-Datensatz wird ausgegeben
if (filters.time_count == 0) return 1;
int has_include_filters = 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++) {
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) {
has_include_filters = 1;
has_include_filters = 1; // Flag für inlusive Filter
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 {
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) {
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;
}
// Prüfen aller Filter im AND-Modus oder OR-Modus (combination_mode) pro Log-Eintrag
int passes_filter(int entry_index) {
int status_match = status_code_matches(all_entries[entry_index].status_code);
int ip_match = ip_address_matches(all_entries[entry_index].ip_address);
@ -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 = 0;
for (int i = 0; i < total_entries; i++) {
@ -546,8 +569,7 @@ void show_status() {
if (total_entries > 0) {
printf("✅ Log-Daten: %d Einträge geladen\n", total_entries);
printf(" Speicherverbrauch: %zu Bytes (%d Einträge Kapazität)\n",
max_entries * sizeof(struct log_entry), max_entries);
printf(" Speicherverbrauch: %lu Bytes (%d Einträge Kapazität)\n", (unsigned long)(max_entries * sizeof(struct log_entry)), max_entries);
} else {
printf("❌ Keine Log-Daten geladen\n");
}