1599 lines
64 KiB
C

/*
Copyright (c) 2025 Mario Stöckl (mstoeck3@hs-mittweida.de).
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h> // library für Interaktion mit Ordnerstrukturen
#include <sys/stat.h> // library für is_directory: Unterscheidung zwischen Dateien und Ordnern
#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 mem_expand_dynamically() genutzt, um den Speicher zu vergrößern
#define MAX_FILTERS 100
#define MAX_REQUEST_LENGTH 1024 // das hohe Limit ist erforderlich, da teilweise ausufernde JSON-Requests in nginx auflaufen können.
// definiert Variablen für den Filtermodus. FILTER_INCLUDE=0, FILTER_EXCLUDE=1. Verbessert die Lesbarkeit des Codes.
typedef enum {
FILTER_INCLUDE,
FILTER_EXCLUDE
} filter_mode_t;
// 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 {
int day;
int month;
int year;
int hour;
int minute;
int second;
};
// Struktur für die Darstellung eines Standard-NGINX-Logeintrags.
struct log_entry {
char ip_address[50]; // ausreichende Längenbegrenzung für IP-Adressen. Könnte theoretisch auch ipv6 (ungetestet)
char request_method[10]; // GET, POST, PUT, DELETE, PROPFIND ...
char url_path[MAX_REQUEST_LENGTH]; // Pfade können lang werden, insbesodere bei base64-Strings wie oft in modernen Applikationen oder Malware verwendet
int status_code;
int bytes_sent;
struct simple_time time;
char referrer[128];
char user_agent[256];
};
// Struktur für einen Status-Filtereintrag mit Inhalt & Modus
struct status_filter {
int code;
filter_mode_t mode;
};
// für IP-Adressen
struct ip_filter {
char ip_address[50];
filter_mode_t mode;
};
// für Zeit, Start- und Endzeit
struct time_filter {
struct simple_time start_time;
struct simple_time end_time;
filter_mode_t mode;
};
// Filter für User-Agent
struct user_agent_filter {
char pattern[256];
filter_mode_t mode;
};
// Struktur zum erhalten aller Filtereinträge, kann im Dialogbetrieb bearbeitet werden. Mit Zähler.
struct filter_system {
struct status_filter status_filters[MAX_FILTERS];
int status_count;
struct ip_filter ip_filters[MAX_FILTERS];
int ip_count;
struct time_filter time_filters[MAX_FILTERS];
int time_count;
struct user_agent_filter user_agent_filters[MAX_FILTERS];
int user_agent_count;
int combination_mode; // 0=AND-Filter oder 1=OR-Filter
};
// Initialisierung eines Arrays für die Logeinträge und weiterer Startvariablen
struct log_entry *all_entries = NULL;
int max_entries = 0;
int total_entries = 0;
struct filter_system filters = {0};
// Hilfsfunktion für die Erkennung von Leerzeichen
int is_space(char c) {
return (c == ' ' || c == '\t');
}
// 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++;
}
return str;
}
// Kopiert einen Eingabestring von einem Quellbereich zu einem Zielbereich, bis ein Leerzeichen (oder Nullterminator) erreicht wird oder die max. Zeilenlänge nicht überschritten 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) {
destination[i] = source[i];
i++;
}
destination[i] = '\0';
}
// NGINX speichert Timestamps mit Monatskürzel. Umwandlung in Zahlen für maschinelle Verarbeitung.
int month_name_to_number(char* month_name) {
if (strncmp(month_name, "Jan", 3) == 0) return 1;
if (strncmp(month_name, "Feb", 3) == 0) return 2;
if (strncmp(month_name, "Mar", 3) == 0) return 3;
if (strncmp(month_name, "Apr", 3) == 0) return 4;
if (strncmp(month_name, "May", 3) == 0) return 5;
if (strncmp(month_name, "Jun", 3) == 0) return 6;
if (strncmp(month_name, "Jul", 3) == 0) return 7;
if (strncmp(month_name, "Aug", 3) == 0) return 8;
if (strncmp(month_name, "Sep", 3) == 0) return 9;
if (strncmp(month_name, "Oct", 3) == 0) return 10;
if (strncmp(month_name, "Nov", 3) == 0) return 11;
if (strncmp(month_name, "Dec", 3) == 0) return 12;
return 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; // 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("%lu Bytes Speicher werden freigegeben\n", (unsigned long)(max_entries * sizeof(struct log_entry)));
free(all_entries);
all_entries = NULL;
}
max_entries = 0;
total_entries = 0;
}
// sauberes Schließen und bereinigen bei Fehlerstatus, sofern Speicher nicht alloziert werden kann
void cleanup_and_exit() {
printf("Programmende. Speicher wird freigegeben und mit NULL überschrieben.\n");
cleanup_memory();
exit(1);
}
// 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("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("ERROR: Speicher konnte nicht auf %d Einträge erweitert werden, ..\n", max_entries);
printf("ERROR: Benötigter Speicher: %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry)));
cleanup_and_exit();
}
all_entries = new_ptr;
printf("Speicher erfolgreich erweitert auf %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry)));
}
}
void allocate_initial_memory() {
max_entries = INITIAL_ENTRIES; // Startwert 1000, globale Variable
all_entries = malloc(max_entries * sizeof(struct log_entry));
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)));
exit(1); // cleanup_and_exit() nicht nötig, da der Speicherbereich nicht beschrieben wurde - use-after-free unproblematisch
}
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) {
return 0;
}
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"); // 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; // true, wenn die Datei mit .log endet
if (*after_log == '.') {
after_log++;
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; // wenn was anderes als Zahlen nach dem . kommen, gib False zurück
after_log++;
}
return 1; // true, wenn nach .log. noch Zahlen vorhanden sind, wie typisch bei NGINX-Logrotation
}
return 0; // false, wenn nichts zutrifft
}
/*
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.
*/
// Standard-nginx-accesslog:
// Standard-nginx-accesslog:
// 107.170.27.248 - - [31/Aug/2025:00:11:42 +0000] "GET /.git/config HTTP/1.1" 400 255 "-" "Mozilla/5.0; Keydrop.io/1.0(onlyscans.com/about);" "-"
int parse_simple_log_line(char* line, int entry_index) { // Nimmt den Pointer auf die Zeile und einen Index entgegen - dieser ist anfangs 0 (globale Variable) und wird pro Eintrag inkrementiert
printf("Parsing line: %s\n", line);
char* current_pos = line;
// leere Zeichen am Anfang überspringen
current_pos = skip_spaces(current_pos);
// kopieren der IP-Adresse in die globale all_entries Struktur unter gegebenen index
// 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);" "-"
// ^
copy_until_space(all_entries[entry_index].ip_address, current_pos, sizeof(all_entries[entry_index].ip_address));
// weiter zum nächsten Leerzeichen
while (*current_pos != ' ' && *current_pos != '\0') 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);" "-"
// ^
// überspringe Leerzeichen
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);" "-"
// ^
// mehrmaliges Überspringen des Wertes, der nicht von Interesse ist, Überspringen des Leerzeichens
while (*current_pos != ' ' && *current_pos != '\0') current_pos++;
current_pos = skip_spaces(current_pos);
// 107.170.27.248 - - [31/Aug/2025:00:11:42 +0000] "GET /.git/config HTTP/1.1" 400 255 "-" "Mozilla/5.0; Keydrop.io/1.0(onlyscans.com/about);" "-"
// ^
while (*current_pos != ' ' && *current_pos != '\0') current_pos++;
current_pos = skip_spaces(current_pos);
// 107.170.27.248 - - [31/Aug/2025:00:11:42 +0000] "GET /.git/config HTTP/1.1" 400 255 "-" "Mozilla/5.0; Keydrop.io/1.0(onlyscans.com/about);" "-"
// ^
// Timestamp-Parsing
if (*current_pos == '[') {
current_pos++;
// 107.170.27.248 - - [31/Aug/2025:00:11:42 +0000] "GET /.git/config HTTP/1.1" 400 255 "-" "Mozilla/5.0; Keydrop.io/1.0(onlyscans.com/about);" "-"
// ^
// es folgt nach und nach das Einlesen von Datum und Uhrzeit, und wiederholtes Verschieben des Pointers
all_entries[entry_index].time.day = 0;
while (*current_pos >= '0' && *current_pos <= '9') {
all_entries[entry_index].time.day = all_entries[entry_index].time.day * 10 + (*current_pos - '0');
current_pos++;
}
if (*current_pos == '/') current_pos++;
char month_str[4] = {0};
int month_pos = 0;
while (*current_pos != '/' && *current_pos != '\0' && month_pos < 3) {
month_str[month_pos] = *current_pos;
month_pos++;
current_pos++;
}
all_entries[entry_index].time.month = month_name_to_number(month_str);
if (*current_pos == '/') current_pos++;
all_entries[entry_index].time.year = 0;
while (*current_pos >= '0' && *current_pos <= '9') {
all_entries[entry_index].time.year = all_entries[entry_index].time.year * 10 + (*current_pos - '0');
current_pos++;
}
if (*current_pos == ':') current_pos++;
all_entries[entry_index].time.hour = 0;
while (*current_pos >= '0' && *current_pos <= '9') {
all_entries[entry_index].time.hour = all_entries[entry_index].time.hour * 10 + (*current_pos - '0');
current_pos++;
}
if (*current_pos == ':') current_pos++;
all_entries[entry_index].time.minute = 0;
while (*current_pos >= '0' && *current_pos <= '9') {
all_entries[entry_index].time.minute = all_entries[entry_index].time.minute * 10 + (*current_pos - '0');
current_pos++;
}
if (*current_pos == ':') current_pos++;
all_entries[entry_index].time.second = 0;
while (*current_pos >= '0' && *current_pos <= '9') {
all_entries[entry_index].time.second = all_entries[entry_index].time.second * 10 + (*current_pos - '0');
current_pos++;
}
// der Zeitzonen-Deskriptor wird übersprungen
while (*current_pos != ']' && *current_pos != '\0') current_pos++;
if (*current_pos == ']') current_pos++;
// 107.170.27.248 - - [31/Aug/2025:00:11:42 +0000] "GET /.git/config HTTP/1.1" 400 255 "-" "Mozilla/5.0; Keydrop.io/1.0(onlyscans.com/about);" "-"
// ^
} 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]\n\n");
cleanup_and_exit();
}
current_pos = skip_spaces(current_pos);
// Weiter mit dem String innerhalb "", aus dem die HTTP-Methode und der URL-Pfad zu entnehmen ist
// Enhanced parsing to handle malformed binary requests gracefully
if (*current_pos == '"') {
current_pos++;
// 107.170.27.248 - - [31/Aug/2025:00:11:42 +0000] "GET /.git/config HTTP/1.1" 400 255 "-" "Mozilla/5.0; Keydrop.io/1.0(onlyscans.com/about);" "-"
// ^
// First, try to read what should be the HTTP method
char temp_method[50];
copy_until_space(temp_method, current_pos, sizeof(temp_method));
// Check if it looks like a valid HTTP method (starts with letters, reasonable length)
int is_valid_method = 1;
if (strlen(temp_method) == 0 || strlen(temp_method) > 10) {
is_valid_method = 0;
} else {
// Check if method contains only letters (no binary data)
for (int i = 0; temp_method[i] != '\0'; i++) {
if (!((temp_method[i] >= 'A' && temp_method[i] <= 'Z') ||
(temp_method[i] >= 'a' && temp_method[i] <= 'z'))) {
is_valid_method = 0;
break;
}
}
}
if (is_valid_method) {
// Normal parsing: HTTP-Methode bis zum nächsten Leerzeichen einlesen und speichern
strcpy(all_entries[entry_index].request_method, temp_method);
while (*current_pos != ' ' && *current_pos != '\0') current_pos++;
current_pos = skip_spaces(current_pos);
// 107.170.27.248 - - [31/Aug/2025:00:11:42 +0000] "GET /.git/config HTTP/1.1" 400 255 "-" "Mozilla/5.0; Keydrop.io/1.0(onlyscans.com/about);" "-"
// ^
// Einlesen des URL-Path bis zum abschließenden "
int i = 0;
while (*current_pos != ' ' && *current_pos != '"' && *current_pos != '\0' && i < sizeof(all_entries[entry_index].url_path) - 1) {
all_entries[entry_index].url_path[i] = *current_pos;
i++;
current_pos++;
}
all_entries[entry_index].url_path[i] = '\0';
while (*current_pos != '"' && *current_pos != '\0') current_pos++;
if (*current_pos == '"') {
current_pos++;
}
} else {
// in NGINX treten gelegentlich fehlerhafte Requests auf, die binäre Daten übersenden, sodass normales parsen nicht möglich ist.
// der entsprechende Eintrag wird daher mit dem String "MALFORMED" repräsentiert
strcpy(all_entries[entry_index].request_method, "MALFORMED");
// Read entire quoted content into url_path for forensic analysis
int i = 0;
while (*current_pos != '"' && *current_pos != '\0' &&
i < sizeof(all_entries[entry_index].url_path) - 1) {
all_entries[entry_index].url_path[i] = *current_pos;
i++;
current_pos++;
}
all_entries[entry_index].url_path[i] = '\0';
// zum Ende des request-strings vorarbeiten, wenn der String zu lang war.
while (*current_pos != '"' && *current_pos != '\0') {
current_pos++;
}
if (*current_pos == '"') current_pos++;
printf("INFO: Fehlerhaften Logeintrag entdeckt, speichere mit MALFORMED-Eintrag.\n");
}
// 107.170.27.248 - - [31/Aug/2025:00:11:42 +0000] "GET /.git/config HTTP/1.1" 400 255 "-" "Mozilla/5.0; Keydrop.io/1.0(onlyscans.com/about);" "-"
// ^
} else {
printf("ERROR: Unerwartetes Log-Format. Lediglich mit standard-nginx-accesslog kompatibel.\nDer Fehler ist beim Prüfen der HTTP-Methode aufgetreten. Diese steht innerhalb eines Strings zusammen mit dem URL-Pfad:\n\"GET /.git/config HTTP/1.1\"\n\n");
cleanup_and_exit();
}
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);" "-"
// ^
// Parsen ded HTTP-Status Codes, der eine Zahl sein muss
all_entries[entry_index].status_code = 0;
while (*current_pos >= '0' && *current_pos <= '9') {
all_entries[entry_index].status_code = all_entries[entry_index].status_code * 10 + (*current_pos - '0');
current_pos++;
}
current_pos = skip_spaces(current_pos);
// genauso mit bytegröße der Anfrage
// 107.170.27.248 - - [31/Aug/2025:00:11:42 +0000] "GET /.git/config HTTP/1.1" 400 255 "-" "Mozilla/5.0; Keydrop.io/1.0(onlyscans.com/about);" "-"
// ^
all_entries[entry_index].bytes_sent = 0;
while (*current_pos >= '0' && *current_pos <= '9') {
all_entries[entry_index].bytes_sent = all_entries[entry_index].bytes_sent * 10 + (*current_pos - '0');
current_pos++;
}
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;
}
// TODO
void load_regular_file(char* filename) {
FILE* file = fopen(filename, "r");
if (file == NULL) {
printf("ERROR: Kann Datei '%s' nicht öffnen!\n", filename);
return;
}
printf("Lade Datei: %s\n", filename);
char line[MAX_LINE_LENGTH_BUF];
int loaded_from_this_file = 0;
while (fgets(line, sizeof(line), file) != NULL) {
mem_expand_dynamically();
if (parse_simple_log_line(line, total_entries)) {
total_entries++;
loaded_from_this_file++;
}
}
fclose(file);
printf(" -> %d Einträge aus dieser Datei geladen.\n", loaded_from_this_file);
}
void load_log_file(char* path) {
total_entries = 0;
if (is_directory(path)) {
printf("Verzeichnis erkannt: %s\n", path);
printf("Suche nach .log Dateien...\n");
DIR* dir = opendir(path);
if (dir == NULL) {
printf("FEHLER: Kann Verzeichnis '%s' nicht öffnen!\n", path);
return;
}
struct dirent* entry;
int files_found = 0;
while ((entry = readdir(dir)) != NULL) {
char* filename = entry->d_name;
if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0) {
continue;
}
if (is_log_file(filename)) {
char full_path[512];
snprintf(full_path, sizeof(full_path), "%s/%s", path, filename);
load_regular_file(full_path);
files_found++;
} else if (strstr(filename, ".gz") != NULL) {
printf("INFO: .gz Datei '%s' übersprungen (nicht unterstützt in dieser Version)\n", filename);
printf(" Tipp: Dekomprimieren Sie mit 'gunzip %s'\n", filename);
}
}
closedir(dir);
if (files_found == 0) {
printf("Keine .log Dateien im Verzeichnis gefunden.\n");
printf("Tipp: Für .gz Dateien verwenden Sie 'gunzip *.gz' zum Dekomprimieren\n");
} else {
printf("Insgesamt %d .log Dateien verarbeitet.\n", files_found);
}
} else {
printf("Einzelne Datei erkannt: %s\n", path);
if (strstr(path, ".gz") != NULL) {
printf("FEHLER: .gz Dateien werden in dieser Version nicht unterstützt!\n");
printf("Lösung: Dekomprimieren Sie die Datei zuerst:\n");
printf(" gunzip %s\n", path);
printf(" Dann: %s %.*s\n", "PROGRAMM", (int)(strlen(path)-3), path);
return;
} else {
load_regular_file(path);
}
}
printf("Erfolgreich %d Einträge insgesamt geladen.\n", total_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) {
if (filters.status_count == 0) return 1;
int has_include_filters = 0;
int include_match = 0;
for (int i = 0; i < filters.status_count; i++) {
if (filters.status_filters[i].mode == FILTER_INCLUDE) {
has_include_filters = 1;
if (filters.status_filters[i].code == status_code) {
include_match = 1;
}
} else {
if (filters.status_filters[i].code == status_code) {
return 0;
}
}
}
if (has_include_filters) {
return include_match;
}
return 1;
}
int ip_address_matches(char* ip_address) {
if (filters.ip_count == 0) return 1;
int has_include_filters = 0;
int include_match = 0;
for (int i = 0; i < filters.ip_count; i++) {
if (filters.ip_filters[i].mode == FILTER_INCLUDE) {
has_include_filters = 1;
if (strcmp(filters.ip_filters[i].ip_address, ip_address) == 0) {
include_match = 1;
}
} else {
if (strcmp(filters.ip_filters[i].ip_address, ip_address) == 0) {
return 0;
}
}
}
if (has_include_filters) {
return include_match;
}
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); // 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; // Flag für inlusive Filter
if (in_range) {
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; // 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; // 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);
int time_match = time_matches(all_entries[entry_index].time);
int user_agent_match = user_agent_matches(all_entries[entry_index].user_agent);
if (filters.combination_mode == 0) {
return status_match && ip_match && time_match && user_agent_match;
} else {
int total_filters = filters.status_count + filters.ip_count + filters.time_count + filters.user_agent_count;
if (total_filters == 0) return 1;
return status_match || ip_match || time_match || user_agent_match;
}
}
// 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++) {
if (passes_filter(i)) {
count++;
}
}
return count;
}
void show_status() {
printf("\n========== SYSTEM STATUS ==========\n");
if (total_entries > 0) {
printf("✅ Log-Daten: %d Einträge geladen\n", total_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");
}
printf("\n🔍 Aktive Filter:\n");
int total_filters = filters.status_count + filters.ip_count + filters.time_count + filters.user_agent_count; // UPDATE THIS LINE
if (total_filters == 0) {
printf(" Keine Filter gesetzt\n");
printf(" Filter-Modus: %s\n", filters.combination_mode == 0 ? "AND" : "OR");
} else {
printf(" Filter-Modus: %s\n", filters.combination_mode == 0 ? "AND" : "OR");
if (filters.status_count > 0) {
printf(" Status-Codes (%d): ", filters.status_count);
for (int i = 0; i < filters.status_count; i++) {
char mode_char = (filters.status_filters[i].mode == FILTER_EXCLUDE) ? '!' : ' ';
printf("%c%d", mode_char, filters.status_filters[i].code);
if (i < filters.status_count - 1) printf(", ");
}
printf("\n");
}
if (filters.ip_count > 0) {
printf(" IP-Adressen (%d): ", filters.ip_count);
for (int i = 0; i < filters.ip_count; i++) {
char mode_char = (filters.ip_filters[i].mode == FILTER_EXCLUDE) ? '!' : ' ';
printf("%c%s", mode_char, filters.ip_filters[i].ip_address);
if (i < filters.ip_count - 1) printf(", ");
}
printf("\n");
}
if (filters.user_agent_count > 0) {
printf(" User-Agent Pattern (%d): ", filters.user_agent_count);
for (int i = 0; i < filters.user_agent_count; i++) {
char mode_char = (filters.user_agent_filters[i].mode == FILTER_EXCLUDE) ? '!' : ' ';
printf("%c\"%s\"", mode_char, filters.user_agent_filters[i].pattern);
if (i < filters.user_agent_count - 1) printf(", ");
}
printf("\n");
}
if (filters.time_count > 0) {
printf(" Zeiträume (%d):\n", filters.time_count);
for (int i = 0; i < filters.time_count; i++) {
char mode_char = (filters.time_filters[i].mode == FILTER_EXCLUDE) ? '!' : ' ';
printf(" %c%02d.%02d.%d %02d:%02d:%02d - %02d.%02d.%d %02d:%02d:%02d\n",
mode_char,
filters.time_filters[i].start_time.day,
filters.time_filters[i].start_time.month,
filters.time_filters[i].start_time.year,
filters.time_filters[i].start_time.hour,
filters.time_filters[i].start_time.minute,
filters.time_filters[i].start_time.second,
filters.time_filters[i].end_time.day,
filters.time_filters[i].end_time.month,
filters.time_filters[i].end_time.year,
filters.time_filters[i].end_time.hour,
filters.time_filters[i].end_time.minute,
filters.time_filters[i].end_time.second);
}
}
}
if (total_entries > 0) {
int filtered_count = count_filtered_entries();
printf("\n📊 Ergebnis: %d von %d Einträgen entsprechen den Filtern\n", filtered_count, total_entries);
}
printf("===================================\n");
}
long long time_to_unix_microseconds(struct simple_time time) {
int days_since_1970 = (time.year - 1970) * 365 + (time.year - 1970) / 4;
int days_in_months[] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
if (time.month >= 1 && time.month <= 12) {
days_since_1970 += days_in_months[time.month - 1];
}
if (time.month > 2 && ((time.year % 4 == 0 && time.year % 100 != 0) || time.year % 400 == 0)) {
days_since_1970 += 1;
}
days_since_1970 += time.day - 1;
long long seconds = (long long)days_since_1970 * 24 * 60 * 60;
seconds += time.hour * 60 * 60;
seconds += time.minute * 60;
seconds += time.second;
return seconds * 1000000LL;
}
void format_iso8601_time(struct simple_time 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);
}
void export_filtered_entries() {
printf("Dateiname für Timesketch Export eingeben (ohne .csv): ");
char filename[95];
if (scanf("%94s", filename) != 1) {
printf("FEHLER: Ungültiger Dateiname!\n");
clear_input_buffer();
return;
}
clear_input_buffer();
strcat(filename, ".csv");
FILE* file = fopen(filename, "w");
if (file == NULL) {
printf("ERROR: Kann Datei '%s' nicht erstellen!\n", filename);
return;
}
fprintf(file, "message,datetime,timestamp_desc,timestamp,ip_address,method,url_path,status_code,bytes_sent,user_agent\n");
int exported_count = 0;
char iso_datetime[32];
char message_text[300];
for (int i = 0; i < total_entries; i++) {
if (passes_filter(i)) {
format_iso8601_time(all_entries[i].time, iso_datetime, sizeof(iso_datetime));
long long unix_timestamp = time_to_unix_microseconds(all_entries[i].time);
snprintf(message_text, sizeof(message_text),
"%s %s %s - Status: %d, Bytes: %d",
all_entries[i].ip_address,
all_entries[i].request_method,
all_entries[i].url_path,
all_entries[i].status_code,
all_entries[i].bytes_sent,
all_entries[i].user_agent);
fprintf(file, "\"%s\",\"%s\",\"HTTP Access Log\",%lld,\"%s\",\"%s\",\"%s\",%d,%d\n",
message_text,
iso_datetime,
unix_timestamp,
all_entries[i].ip_address,
all_entries[i].request_method,
all_entries[i].url_path,
all_entries[i].status_code,
all_entries[i].bytes_sent,
all_entries[i].user_agent);
exported_count++;
}
}
fclose(file);
printf("%d Logeinträge erfolgreich als Timesketch-kompatible CSV-Datei nach '%s' exportiert.\n", exported_count, filename);
}
struct ip_stat {
char ip_address[50];
int count;
};
void show_top_10_ips() {
struct ip_stat ip_stats[1000];
int unique_ips = 0;
for (int i = 0; i < total_entries; i++) {
if (!passes_filter(i)) continue;
char* current_ip = all_entries[i].ip_address;
int found_index = -1;
for (int j = 0; j < unique_ips; j++) {
if (strcmp(ip_stats[j].ip_address, current_ip) == 0) {
found_index = j;
break;
}
}
if (found_index >= 0) {
ip_stats[found_index].count++;
} else {
if (unique_ips < 1000) {
strcpy(ip_stats[unique_ips].ip_address, current_ip);
ip_stats[unique_ips].count = 1;
unique_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];
ip_stats[j] = ip_stats[j + 1];
ip_stats[j + 1] = temp;
}
}
}
printf("\n=== TOP 10 IP-ADRESSEN ===\n");
printf("Rang | IP-Adresse | Anzahl Anfragen\n");
printf("-----|------------------|----------------\n");
int show_count = (unique_ips < 10) ? unique_ips : 10;
for (int i = 0; i < show_count; i++) {
printf("%-4d | %-16s | %d\n", i + 1, ip_stats[i].ip_address, ip_stats[i].count);
}
if (unique_ips == 0) {
printf("Keine IP-Adressen in den gefilterten Daten gefunden.\n");
} else {
printf("\nInsgesamt %d verschiedene IP-Adressen gefunden.\n", unique_ips);
}
}
void show_top_user_agents() {
struct user_agent_stat {
char user_agent[256];
int count;
};
struct user_agent_stat agent_stats[1000];
int unique_agents = 0;
for (int i = 0; i < total_entries; i++) {
if (!passes_filter(i)) continue;
char* current_agent = all_entries[i].user_agent;
int found_index = -1;
for (int j = 0; j < unique_agents; j++) {
if (strcmp(agent_stats[j].user_agent, current_agent) == 0) {
found_index = j;
break;
}
}
if (found_index >= 0) {
agent_stats[found_index].count++;
} else {
if (unique_agents < 1000) {
strcpy(agent_stats[unique_agents].user_agent, current_agent);
agent_stats[unique_agents].count = 1;
unique_agents++;
}
}
}
// Sort by count (descending)
for (int i = 0; i < unique_agents - 1; i++) {
for (int j = 0; j < unique_agents - i - 1; j++) {
if (agent_stats[j].count < agent_stats[j + 1].count) {
struct user_agent_stat temp = agent_stats[j];
agent_stats[j] = agent_stats[j + 1];
agent_stats[j + 1] = temp;
}
}
}
printf("\n=== TOP 10 USER AGENTS ===\n");
printf("Rang | User Agent | Anzahl Anfragen\n");
printf("-----|--------------------------------------|----------------\n");
int show_count = (unique_agents < 10) ? unique_agents : 10;
for (int i = 0; i < show_count; i++) {
printf("%-4d | %-34s | %d\n", i + 1, agent_stats[i].user_agent, agent_stats[i].count);
}
if (unique_agents == 0) {
printf("Keine User Agents in den gefilterten Daten gefunden.\n");
} else {
printf("\nInsgesamt %d verschiedene User Agents gefunden.\n", unique_agents);
}
}
void show_filtered_entries() {
int shown_count = 0;
printf("\n=== GEFILTERTE LOG-EINTRÄGE ===\n");
printf("IP-Adresse | Methode | URL | Status | Bytes | User Agent | Zeit\n");
printf("-----------------|---------|------------------------|--------|-------|--------------------------------------|------------------\n");
for (int i = 0; i < total_entries; i++) {
if (passes_filter(i)) {
printf("%-16s | %-7s | %-22s | %-6d | %-5d | %02d.%02d.%d %02d:%02d:%02d\n",
all_entries[i].ip_address,
all_entries[i].request_method,
all_entries[i].url_path,
all_entries[i].status_code,
all_entries[i].bytes_sent,
all_entries[i].user_agent,
all_entries[i].time.day,
all_entries[i].time.month,
all_entries[i].time.year,
all_entries[i].time.hour,
all_entries[i].time.minute,
all_entries[i].time.second);
shown_count++;
}
}
printf("\nInsgesamt %d Einträge gefunden.\n", shown_count);
if (shown_count == 0) {
printf("Keine Einträge gefunden, die dem Filter entsprechen.\n");
}
}
void show_statistics() {
int count_200 = 0;
int count_404 = 0;
int count_500 = 0;
int total_bytes = 0;
int filtered_count = 0;
printf("\n=== STATISTIKEN ===\n");
for (int i = 0; i < total_entries; i++) {
if (passes_filter(i)) {
filtered_count++;
total_bytes += all_entries[i].bytes_sent;
if (all_entries[i].status_code == 200) count_200++;
else if (all_entries[i].status_code == 404) count_404++;
else if (all_entries[i].status_code >= 500) count_500++;
}
}
printf("Gefilterte Einträge: %d von %d\n", filtered_count, total_entries);
printf("Erfolgreiche Anfragen (200): %d\n", count_200);
printf("Nicht gefunden (404): %d\n", count_404);
printf("Server-Fehler (5xx): %d\n", count_500);
printf("Gesamte übertragene Bytes: %d\n", total_bytes);
if (filtered_count > 0) {
printf("Durchschnittliche Bytes pro Anfrage: %d\n", total_bytes / filtered_count);
}
}
void menu_set_filters() {
int choice = 0;
while (choice != 4) {
show_status();
printf("\n=== FILTER SETZEN ===\n");
printf("1. Status-Code Filter hinzufügen (exakte Suche)\n");
printf("2. IP-Adresse Filter hinzufügen (exakte Suche)\n");
printf("3. Zeitraum Filter hinzufügen (interaktiv)\n");
printf("4. User-Agent-Filter setzen (Freitext)\n");
printf("5. Zurück zum Filter-Menü\n");
printf("Auswahl: ");
choice = read_safe_integer();
if (choice == 1) {
if (filters.status_count >= MAX_FILTERS) {
printf("FEHLER: Maximale Anzahl Status-Code Filter erreicht (%d)!\n", MAX_FILTERS);
continue;
}
printf("Status Code eingeben (z.B. 200, 404, 500): ");
int status = read_safe_integer();
if (status == -1) {
printf("FEHLER: Ungültiger Status Code!\n");
} else {
printf("Filter-Typ wählen:\n");
printf("1. Einschließen (nur Status %d anzeigen)\n", status);
printf("2. Ausschließen (Status %d NICHT anzeigen)\n", status);
printf("Auswahl: ");
int filter_type = read_safe_integer();
if (filter_type == 1 || filter_type == 2) {
filters.status_filters[filters.status_count].code = status;
filters.status_filters[filters.status_count].mode = (filter_type == 2) ? FILTER_EXCLUDE : FILTER_INCLUDE;
filters.status_count++;
printf("✅ Status-Code Filter hinzugefügt. Total: %d\n", filters.status_count);
} else {
printf("FEHLER: Ungültiger Filter-Typ!\n");
}
}
} else if (choice == 2) {
if (filters.ip_count >= MAX_FILTERS) {
printf("FEHLER: Maximale Anzahl IP-Filter erreicht (%d)!\n", MAX_FILTERS);
continue;
}
printf("IP-Adresse eingeben: ");
char ip[50];
if (scanf("%49s", ip) == 1) {
printf("Filter-Typ wählen:\n");
printf("1. Einschließen (nur IP %s anzeigen)\n", ip);
printf("2. Ausschließen (IP %s NICHT anzeigen)\n", ip);
printf("Auswahl: ");
int filter_type = read_safe_integer();
if (filter_type == 1 || filter_type == 2) {
strcpy(filters.ip_filters[filters.ip_count].ip_address, ip);
filters.ip_filters[filters.ip_count].mode = (filter_type == 2) ? FILTER_EXCLUDE : FILTER_INCLUDE;
filters.ip_count++;
printf("✅ IP-Filter hinzugefügt. Total: %d\n", filters.ip_count);
} else {
printf("FEHLER: Ungültiger Filter-Typ!\n");
}
} else {
printf("FEHLER: Ungültige IP-Adresse!\n");
clear_input_buffer();
}
} else if (choice == 3) {
if (filters.time_count >= MAX_FILTERS) {
printf("FEHLER: Maximale Anzahl Zeitraum-Filter erreicht (%d)!\n", MAX_FILTERS);
continue;
}
printf("\n=== ZEITRAUM FILTER HINZUFÜGEN ===\n");
struct time_filter new_time_filter = {0};
printf("Startzeit eingeben:\n");
printf("Jahr (z.B. 2023): ");
int start_year = read_safe_integer();
if (start_year == -1) { printf("FEHLER: Ungültiges Jahr!\n"); continue; }
printf("Monat (1-12): ");
int start_month = read_safe_integer();
if (start_month == -1 || start_month < 1 || start_month > 12) {
printf("FEHLER: Ungültiger Monat!\n"); continue;
}
printf("Tag (1-31): ");
int start_day = read_safe_integer();
if (start_day == -1 || start_day < 1 || start_day > 31) {
printf("FEHLER: Ungültiger Tag!\n"); continue;
}
printf("Stunde (0-23): ");
int start_hour = read_safe_integer();
if (start_hour == -1 || start_hour < 0 || start_hour > 23) {
printf("FEHLER: Ungültige Stunde!\n"); continue;
}
printf("Minute (0-59): ");
int start_minute = read_safe_integer();
if (start_minute == -1 || start_minute < 0 || start_minute > 59) {
printf("FEHLER: Ungültige Minute!\n"); continue;
}
printf("Sekunde (0-59): ");
int start_second = read_safe_integer();
if (start_second == -1 || start_second < 0 || start_second > 59) {
printf("FEHLER: Ungültige Sekunde!\n"); continue;
}
printf("\nEndzeit eingeben:\n");
printf("Jahr (z.B. 2023): ");
int end_year = read_safe_integer();
if (end_year == -1) { printf("FEHLER: Ungültiges Jahr!\n"); continue; }
printf("Monat (1-12): ");
int end_month = read_safe_integer();
if (end_month == -1 || end_month < 1 || end_month > 12) {
printf("FEHLER: Ungültiger Monat!\n"); continue;
}
printf("Tag (1-31): ");
int end_day = read_safe_integer();
if (end_day == -1 || end_day < 1 || end_day > 31) {
printf("FEHLER: Ungültiger Tag!\n"); continue;
}
printf("Stunde (0-23): ");
int end_hour = read_safe_integer();
if (end_hour == -1 || end_hour < 0 || end_hour > 23) {
printf("FEHLER: Ungültige Stunde!\n"); continue;
}
printf("Minute (0-59): ");
int end_minute = read_safe_integer();
if (end_minute == -1 || end_minute < 0 || end_minute > 59) {
printf("FEHLER: Ungültige Minute!\n"); continue;
}
printf("Sekunde (0-59): ");
int end_second = read_safe_integer();
if (end_second == -1 || end_second < 0 || end_second > 59) {
printf("FEHLER: Ungültige Sekunde!\n"); continue;
}
printf("\nFilter-Typ wählen:\n");
printf("1. Einschließen (nur Ereignisse IN diesem Zeitraum anzeigen)\n");
printf("2. Ausschließen (Ereignisse in diesem Zeitraum NICHT anzeigen)\n");
printf("Auswahl: ");
int filter_type = read_safe_integer();
if (filter_type != 1 && filter_type != 2) {
printf("FEHLER: Ungültiger Filter-Typ!\n");
continue;
}
new_time_filter.start_time.year = start_year;
new_time_filter.start_time.month = start_month;
new_time_filter.start_time.day = start_day;
new_time_filter.start_time.hour = start_hour;
new_time_filter.start_time.minute = start_minute;
new_time_filter.start_time.second = start_second;
new_time_filter.end_time.year = end_year;
new_time_filter.end_time.month = end_month;
new_time_filter.end_time.day = end_day;
new_time_filter.end_time.hour = end_hour;
new_time_filter.end_time.minute = end_minute;
new_time_filter.end_time.second = end_second;
new_time_filter.mode = (filter_type == 2) ? FILTER_EXCLUDE : FILTER_INCLUDE;
filters.time_filters[filters.time_count] = new_time_filter;
filters.time_count++;
printf("✅ Zeitraum-Filter hinzugefügt. Total: %d\n", filters.time_count);
} else if (choice == 4) {
if (filters.user_agent_count >= MAX_FILTERS) {
printf("FEHLER: Maximale Anzahl User-Agent Filter erreicht (%d)!\n", MAX_FILTERS);
continue;
}
printf("User-Agent Suchtext eingeben (z.B. 'chrome', 'bot', 'upti'): ");
char pattern[256];
if (scanf("%255s", pattern) == 1) {
printf("Filter-Typ wählen:\n");
printf("1. Einschließen (nur User-Agents mit '%s' anzeigen)\n", pattern);
printf("2. Ausschließen (User-Agents mit '%s' NICHT anzeigen)\n", pattern);
printf("Auswahl: ");
int filter_type = read_safe_integer();
if (filter_type == 1 || filter_type == 2) {
strcpy(filters.user_agent_filters[filters.user_agent_count].pattern, pattern);
filters.user_agent_filters[filters.user_agent_count].mode = (filter_type == 2) ? FILTER_EXCLUDE : FILTER_INCLUDE;
filters.user_agent_count++;
printf("✅ User-Agent Filter hinzugefügt. Total: %d\n", filters.user_agent_count);
} else {
printf("FEHLER: Ungültiger Filter-Typ!\n");
}
} else {
printf("FEHLER: Ungültiger Suchtext!\n");
clear_input_buffer();
}
} else if (choice == 5) {
return;
} else if (choice != -1) {
printf("Ungültige Auswahl! Bitte wählen Sie 1-5.\n");
}
}
}
void menu_delete_filters() {
int total_filters = filters.status_count + filters.ip_count + filters.time_count + filters.user_agent_count; // UPDATE THIS LINE
if (total_filters == 0) {
printf("Keine Filter gesetzt zum Löschen.\n");
return;
}
printf("\n=== FILTER LÖSCHEN ===\n");
printf("Aktuell gesetzte Filter:\n");
int filter_index = 1;
for (int i = 0; i < filters.status_count; i++) {
char* mode_str = (filters.status_filters[i].mode == FILTER_EXCLUDE) ? "(ausschließen)" : "(einschließen)";
printf("%d. Status-Code: %d %s\n", filter_index++, filters.status_filters[i].code, mode_str);
}
for (int i = 0; i < filters.ip_count; i++) {
char* mode_str = (filters.ip_filters[i].mode == FILTER_EXCLUDE) ? "(ausschließen)" : "(einschließen)";
printf("%d. IP-Adresse: %s %s\n", filter_index++, filters.ip_filters[i].ip_address, mode_str);
}
for (int i = 0; i < filters.user_agent_count; i++) {
char* mode_str = (filters.user_agent_filters[i].mode == FILTER_EXCLUDE) ? "(ausschließen)" : "(einschließen)";
printf("%d. User-Agent: \"%s\" %s\n", filter_index++, filters.user_agent_filters[i].pattern, mode_str);
}
for (int i = 0; i < filters.time_count; i++) {
char* mode_str = (filters.time_filters[i].mode == FILTER_EXCLUDE) ? "(ausschließen)" : "(einschließen)";
printf("%d. Zeitraum: %02d.%02d.%d %02d:%02d:%02d - %02d.%02d.%d %02d:%02d:%02d %s\n",
filter_index++,
filters.time_filters[i].start_time.day,
filters.time_filters[i].start_time.month,
filters.time_filters[i].start_time.year,
filters.time_filters[i].start_time.hour,
filters.time_filters[i].start_time.minute,
filters.time_filters[i].start_time.second,
filters.time_filters[i].end_time.day,
filters.time_filters[i].end_time.month,
filters.time_filters[i].end_time.year,
filters.time_filters[i].end_time.hour,
filters.time_filters[i].end_time.minute,
filters.time_filters[i].end_time.second,
mode_str);
}
printf("Welchen Filter möchten Sie löschen (1-%d) oder 0 für Abbrechen: ", total_filters);
int choice = read_safe_integer();
if (choice == 0) {
return;
}
if (choice < 1 || choice > total_filters) {
printf("FEHLER: Ungültige Auswahl!\n");
return;
}
int current_index = 1;
for (int i = 0; i < filters.status_count; i++) {
if (current_index == choice) {
for (int j = i; j < filters.status_count - 1; j++) {
filters.status_filters[j] = filters.status_filters[j + 1];
}
filters.status_count--;
printf("✅ Status-Code Filter gelöscht.\n");
return;
}
current_index++;
}
for (int i = 0; i < filters.ip_count; i++) {
if (current_index == choice) {
for (int j = i; j < filters.ip_count - 1; j++) {
filters.ip_filters[j] = filters.ip_filters[j + 1];
}
filters.ip_count--;
printf("✅ IP-Filter gelöscht.\n");
return;
}
current_index++;
}
for (int i = 0; i < filters.user_agent_count; i++) {
if (current_index == choice) {
for (int j = i; j < filters.user_agent_count - 1; j++) {
filters.user_agent_filters[j] = filters.user_agent_filters[j + 1];
}
filters.user_agent_count--;
printf("✅ User-Agent Filter gelöscht.\n");
return;
}
current_index++;
}
for (int i = 0; i < filters.time_count; i++) {
if (current_index == choice) {
for (int j = i; j < filters.time_count - 1; j++) {
filters.time_filters[j] = filters.time_filters[j + 1];
}
filters.time_count--;
printf("✅ Zeitraum-Filter gelöscht.\n");
return;
}
current_index++;
}
}
void menu_filter_mode() {
printf("\n=== FILTER-MODUS ===\n");
printf("Aktueller Modus: %s\n", filters.combination_mode == 0 ? "AND (alle müssen zutreffen)" : "OR (einer muss zutreffen)");
printf("1. AND-Modus (alle Filter müssen zutreffen)\n");
printf("2. OR-Modus (mindestens ein Filter muss zutreffen)\n");
printf("Auswahl: ");
int choice = read_safe_integer();
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 != -1) {
printf("Ungültige Auswahl!\n");
}
}
void menu_reset_filters() {
filters.status_count = 0;
filters.ip_count = 0;
filters.time_count = 0;
filters.user_agent_count = 0;
filters.combination_mode = 0;
printf("✅ Alle Filter zurückgesetzt.\n");
}
void menu_filter_management() {
int choice = 0;
while (choice != 5) {
show_status();
printf("\n=== FILTER VERWALTEN ===\n");
printf("1. Filter setzen\n");
printf("2. Filter löschen\n");
printf("3. Filter-Modus (AND/OR)\n");
printf("4. Filter zurücksetzen\n");
printf("5. Zurück zum Hauptmenü\n");
printf("Auswahl: ");
choice = read_safe_integer();
if (choice == 1) {
menu_set_filters();
} 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) {
return;
} else if (choice != -1) {
printf("Ungültige Auswahl! Bitte wählen Sie 1-5.\n");
}
}
}
void menu_show_entries() {
int choice = 0;
while (choice != 4) {
show_status();
printf("\n=== EINTRÄGE ANZEIGEN ===\n");
int filtered_count = count_filtered_entries();
printf("Aktuell %d gefilterte Einträge verfügbar.\n", filtered_count);
printf("1. Vollständig anzeigen\n");
printf("2. Zu .csv exportieren (Timesketch-kompatibel)\n");
printf("3. Top 10 IP-Adressen\n");
printf("4. Top 10 User-Agents\n");
printf("5. Zurück zum Hauptmenü\n");
printf("Auswahl: ");
choice = read_safe_integer();
if (choice == 1) {
if (filtered_count > 1000) {
printf("WARNUNG: %d Einträge sind sehr viele für die Anzeige!\n", filtered_count);
printf("Möchten Sie trotzdem fortfahren? (1=Ja, 0=Nein): ");
int confirm = read_safe_integer();
if (confirm != 1) {
printf("Anzeige abgebrochen. Tipp: Verwenden Sie den Export für große Datenmengen.\n");
continue;
}
}
show_filtered_entries();
} else if (choice == 2) {
export_filtered_entries();
} else if (choice == 3) {
show_top_10_ips();
} else if (choice == 4) {
show_top_user_agents();
} else if (choice == 5) {
return;
} else if (choice != -1) {
printf("Ungültige Auswahl! Bitte wählen Sie 1-4.\n");
}
}
}
void show_main_menu() {
printf("\nHauptmenü:\n");
printf("1. Filter verwalten\n");
printf("2. Anzeigemodi\n");
printf("3. Statistiken anzeigen\n");
printf("4. Beenden\n");
printf("Auswahl: ");
}
int main(int argc, char* argv[]) {
if (argc != 2) {
printf("Verwendung: %s <nginx-log-datei-oder-verzeichnis>\n", argv[0]);
printf("Unterstützte Formate: .log Dateien, sowie für rotierte Logs: .log.1 etc.\n");
printf("Beispiele:\n");
printf(" %s /var/log/nginx/access.log (einzelne .log Datei)\n", argv[0]);
printf(" %s /var/log/nginx/ (Verzeichnis mit .log Dateien)\n", argv[0]);
printf("\nRotierte nginx-Logs (.gz-Archive) müssen manuell dekomprimiert werden:\n");
printf(" 1. Dekomprimieren: gunzip /var/log/nginx/*.gz\n");
printf(" 2. Dann ausführen: %s /var/log/nginx/\n", argv[0]);
return 1;
}
printf("=== NGINX LOG PARSER - VEREINFACHTE VERSION ===\n");
allocate_initial_memory();
load_log_file(argv[1]);
if (total_entries == 0) {
printf("Keine gültigen Log-Einträge gefunden. Überprüfen Sie den Pfad und die Dateiformate.\n");
cleanup_memory();
return 1;
}
int choice = 0;
while (choice != 4) {
show_status();
show_main_menu();
choice = read_safe_integer();
if (choice == -1) {
printf("FEHLER: Bitte geben Sie eine gültige Zahl ein!\n");
continue;
}
if (choice == 1) {
menu_filter_management();
} else if (choice == 2) {
menu_show_entries();
} else if (choice == 3) {
show_statistics();
} else if (choice == 4) {
printf("Auf Wiedersehen!\n");
} else {
printf("Ungültige Auswahl! Bitte wählen Sie 1-4.\n");
}
}
cleanup_memory();
return 0;
}