overcuriousity 58237b1ec4 progress
2025-09-02 15:15:11 +02:00

2230 lines
90 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
#include <time.h> // um aktuelle Zeit zu generieren
#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 1.1 // 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.
#define TOP_X 20 // definiert die Anzahl der Einträge, die in den Top-Listen angezeigt werden sollen
// 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];
char source_file[256];
char parsing_timestamp[32];
};
// Struktur für einen Status-Filtereintrag mit Inhalt & Modus
struct status_filter {
int code;
filter_mode_t mode;
};
struct method_filter {
char pattern[10];
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;
};
// Filter für URL-Pfad/Request
struct url_filter {
char pattern[MAX_REQUEST_LENGTH];
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 method_filter method_filters[MAX_FILTERS];
int method_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;
struct url_filter url_filters[MAX_FILTERS];
int url_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 %f\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)));
}
void get_current_timestamp(char* buffer, int buffer_size) {
time_t raw_time;
struct tm *time_info;
time(&raw_time);
time_info = localtime(&raw_time);
if (time_info != NULL) {
strftime(buffer, buffer_size, "%Y-%m-%d %H:%M:%S", time_info);
} else {
snprintf(buffer, buffer_size, "UNKNOWN");
}
}
// 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:
/*
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
*/
// 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, char* source_file) { // 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;
// 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
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);" "-"
// ^
// lesen der HTTP-Methode in temporäre Variable
char temp_method[50];
copy_until_space(temp_method, current_pos, sizeof(temp_method));
// Längenprüfung des Methodenstrings
int is_valid_method = 1;
if (strlen(temp_method) == 0 || strlen(temp_method) > 10) {
is_valid_method = 0;
} else {
// prüfen, ob die Methode nur ASCII-Zeichen enthält
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 parsen: 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 "ATYPICAL" repräsentiert
strcpy(all_entries[entry_index].request_method, "ATYPICAL");
// 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++;
}
// 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();
}
get_current_timestamp(all_entries[entry_index].parsing_timestamp, sizeof(all_entries[entry_index].parsing_timestamp));
// Dateinamen in das Feld schreiben - strncpy um Buffer overflow zu verhindern
strncpy(all_entries[entry_index].source_file, source_file, sizeof(all_entries[entry_index].source_file) - 1);
// strncpy setzt keinen Nullterminator, dieser muss am Ende eingefügt werden
all_entries[entry_index].source_file[sizeof(all_entries[entry_index].source_file) - 1] = '\0';
return 1;
}
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, filename)) {
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;
// Ausschluss-Filter geht vor
for (int i = 0; i < filters.user_agent_count; i++) {
if (filters.user_agent_filters[i].mode == FILTER_EXCLUDE) {
int pattern_found = search_in_string(user_agent, filters.user_agent_filters[i].pattern);
if (pattern_found) {
return 0; // früheres Verlassen der Schleife, sobald Ausschlussfilter zutrifft
}
}
}
// Prüfung im entsprechenden Modus
int include_count = 0;
int include_matches = 0;
for (int i = 0; i < filters.user_agent_count; i++) {
if (filters.user_agent_filters[i].mode == FILTER_INCLUDE) {
include_count++;
int pattern_found = search_in_string(user_agent, filters.user_agent_filters[i].pattern);
if (pattern_found) {
include_matches++;
if (filters.combination_mode == 1) { // OR-Modus
return 1; // Früheres Verlassen der Schleife bei erstem zutreffendem Einschlussfilter im OR-Modus
}
}
}
}
// Diese Prüfung wird ausgeführt, wenn Einschlussfilter vorhanden sind
if (include_count > 0) {
if (filters.combination_mode == 0) { // AND-Modus
return include_matches == include_count; // Alle Einschlussfilter müssen zutreffen, die Treffer müssen Anzahl der Filter entsprechen
} else { // OR-Modus
return include_matches > 0; // Ausschlussfilter im ODER-Modus - wenn ein beliebiger Eintrag zum Ausschlussfilter passt, wird 0=negativ zurückgegeben
}
}
return 1; // Keine Einschlussfilter, keine zutreffenden Ausschlussfilter, positiver Rückgabewert =1
}
// Filterfunktion für URL-Pfad. Nimmt den Datensatz entgegen und prüft gegen die gesetzten Filter, gibt dann 0 oder 1 zurück
int url_matches(char* url_path) {
if (filters.url_count == 0) return 1;
// Ausschluss-Filter geht vor
for (int i = 0; i < filters.url_count; i++) {
if (filters.url_filters[i].mode == FILTER_EXCLUDE) {
int pattern_found = search_in_string(url_path, filters.url_filters[i].pattern);
if (pattern_found) {
return 0; // früheres Verlassen der Schleife, sobald Ausschlussfilter zutrifft
}
}
}
// Prüfung im entsprechenden Modus
int include_count = 0;
int include_matches = 0;
for (int i = 0; i < filters.url_count; i++) {
if (filters.url_filters[i].mode == FILTER_INCLUDE) {
include_count++;
int pattern_found = search_in_string(url_path, filters.url_filters[i].pattern);
if (pattern_found) {
include_matches++;
if (filters.combination_mode == 1) { // OR-Modus
return 1; // Früheres Verlassen der Schleife bei erstem zutreffendem Einschlussfilter im OR-Modus
}
}
}
}
// Diese Prüfung wird ausgeführt, wenn Einschlussfilter vorhanden sind
if (include_count > 0) {
if (filters.combination_mode == 0) { // AND-Modus
return include_matches == include_count; // Alle Einschlussfilter müssen zutreffen, die Treffer müssen Anzahl der Filter entsprechen
} else { // OR-Modus
return include_matches > 0; // Ausschlussfilter im ODER-Modus - wenn ein beliebiger Eintrag zum Ausschlussfilter passt, wird 0=negativ zurückgegeben
}
}
return 1; // Keine Einschlussfilter, keine zutreffenden Ausschlussfilter, positiver Rückgabewert =1
}
// Filterfunktion für Zugriffsmethode. Nimmt den Datensatz entgegen und prüft gegen die gesetzten Filter, gibt dann 0 oder 1 zurück
int method_matches(char* request_method) {
if (filters.method_count == 0) return 1;
// Ausschluss-Filter geht vor
for (int i = 0; i < filters.method_count; i++) {
if (filters.method_filters[i].mode == FILTER_EXCLUDE) {
int pattern_found = search_in_string(request_method, filters.method_filters[i].pattern);
if (pattern_found) {
return 0; // früheres Verlassen der Schleife, sobald Ausschlussfilter zutrifft
}
}
}
// Prüfung im entsprechenden Modus
int include_count = 0;
int include_matches = 0;
for (int i = 0; i < filters.method_count; i++) {
if (filters.method_filters[i].mode == FILTER_INCLUDE) {
include_count++;
int pattern_found = search_in_string(request_method, filters.method_filters[i].pattern);
if (pattern_found) {
include_matches++;
if (filters.combination_mode == 1) { // OR-Modus
return 1; // Früheres Verlassen der Schleife bei erstem zutreffendem Einschlussfilter im OR-Modus
}
}
}
}
// Diese Prüfung wird ausgeführt, wenn Einschlussfilter vorhanden sind
if (include_count > 0) {
if (filters.combination_mode == 0) { // AND-Modus
return include_matches == include_count; // Alle Einschlussfilter müssen zutreffen, die Treffer müssen Anzahl der Filter entsprechen
} else { // OR-Modus
return 0;
}
}
return 1; // Keine Einschlussfilter, keine zutreffenden Ausschlussfilter, positiver Rückgabewert =1
}
int status_code_matches(int status_code) {
if (filters.status_count == 0) return 1;
// Ausschluss-Filter prüfen: immer übergeordnet gültig
for (int i = 0; i < filters.status_count; i++) {
if (filters.status_filters[i].mode == FILTER_EXCLUDE) {
if (filters.status_filters[i].code == status_code) {
return 0;
}
}
}
// Filter prüfen in entsprechendem Modus
int include_count = 0;
int include_matches = 0;
for (int i = 0; i < filters.status_count; i++) {
if (filters.status_filters[i].mode == FILTER_INCLUDE) {
include_count++;
if (filters.status_filters[i].code == status_code) {
include_matches++;
if (filters.combination_mode == 1) { // OR-Modus
return 1; // positiver Rückgabewert, Schleife wird verlassen sobald erster Treffer im OR-Modus
}
}
}
}
// Diese Prüfung wird ausgeführt, wenn Einschlussfilter vorhanden sind
if (include_count > 0) {
if (filters.combination_mode == 0) { // UND-Modus
return include_matches == include_count; // Filter-Treffer müssen der Anzahl der Statuscode-Filter entsprechen
} else { // OR-Modus
return include_matches > 0; // Ausschlussfilter im ODER-Modus - wenn ein beliebiger Eintrag zum Ausschlussfilter passt, wird 0=negativ zurückgegeben
}
}
return 1; // Keine Einschlussfilter, keine Treffer in den Ausschlussfiltern, positiver Rückgabewert = 1
}
int ip_address_matches(char* ip_address) {
if (filters.ip_count == 0) return 1;
// Prüfen der Ausschlussfilter, sind dem Rest vorgelagert
for (int i = 0; i < filters.ip_count; i++) {
if (filters.ip_filters[i].mode == FILTER_EXCLUDE) {
if (strcmp(filters.ip_filters[i].ip_address, ip_address) == 0) {
return 0; // zutreffender Ausschlussfilter führt zu negativem Rückgabewert
}
}
}
// Prüfung im AND oder im OR-Modus
int include_count = 0;
int include_matches = 0;
for (int i = 0; i < filters.ip_count; i++) {
if (filters.ip_filters[i].mode == FILTER_INCLUDE) {
include_count++;
if (strcmp(filters.ip_filters[i].ip_address, ip_address) == 0) {
include_matches++;
if (filters.combination_mode == 1) { // OR-Modus
return 1; // Früheres Verlassen der Schleife, sofern erster Filter im OR-Modus zutrifft
}
}
}
}
// Diese Prüfung wird ausgeführt, wenn Einschlussfilter vorhanden sind
if (include_count > 0) {
if (filters.combination_mode == 0) { // UND-Modus
return include_matches == include_count; // Filter-Treffer müssen der Anzahl der IP-Adressen-Filter entsprechen
} else { // OR-Modus
return include_matches > 0; // zutreffender Ausschlussfilter führt zu negativem Rückgabewert
}
}
return 1; // Keine Einschlussfilter, keine Treffer in den Ausschlussfiltern, positiver Rückgabewert = 1
}
// 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) {
if (filters.time_count == 0) return 1;
// Übergeordneter Ausschlussfilter
for (int i = 0; i < filters.time_count; i++) {
if (filters.time_filters[i].mode == FILTER_EXCLUDE) {
int in_range = (compare_times(entry_time, filters.time_filters[i].start_time) >= 0 &&
compare_times(entry_time, filters.time_filters[i].end_time) <= 0);
if (in_range) {
return 0; // zutreffender Ausschlussfilter führt zu negativem Rückgabewert, ist den Einschlussfiltern übergeordnet
}
}
}
// Prüfung im entsprechenden Modus
int include_count = 0;
int include_matches = 0;
for (int i = 0; i < filters.time_count; i++) {
if (filters.time_filters[i].mode == FILTER_INCLUDE) {
include_count++;
int in_range = (compare_times(entry_time, filters.time_filters[i].start_time) >= 0 &&
compare_times(entry_time, filters.time_filters[i].end_time) <= 0);
if (in_range) {
include_matches++;
if (filters.combination_mode == 1) { // OR-Modus
return 1; // Sobald der erste Zeitraum im OR-Modus zutrifft, wird die Schleife verlassen
}
}
}
}
// Diese Prüfung wird ausgeführt, wenn Einschlussfilter vorhanden sind
if (include_count > 0) {
if (filters.combination_mode == 0) { // AND-Modus
return include_matches == include_count; // Filter-Treffer müssen der Anzahl der Zeitraum-Filter entsprechen
} else { // OR-Modus
return include_matches > 0; // zutreffender Ausschlussfilter führt zu negativem Rückgabewert
}
}
return 1; // keine Einschlussfilter und keine zutreffenden Ausschlussfilter - positiver Rückgabewert
}
// Prüfen aller Filter im AND-Modus oder OR-Modus (combination_mode) pro Log-Eintrag
int passes_filter(int entry_index) {
if (filters.combination_mode == 0) { // AND-Modus
// alle AND-verknüpften Filter müssen zutreffen
int status_match = status_code_matches(all_entries[entry_index].status_code);
int method_match = method_matches(all_entries[entry_index].request_method);
int ip_match = ip_address_matches(all_entries[entry_index].ip_address);
int time_match = time_matches(all_entries[entry_index].time);
int user_agent_match = user_agent_matches(all_entries[entry_index].user_agent);
int url_match = url_matches(all_entries[entry_index].url_path);
return status_match && ip_match && time_match && user_agent_match && method_match && url_match;
} else { // OR-Modus
// für den geprüften Eintrag muss mindestens eine der Filterkategorien zutreffen.
// Eine Prüfung über Filter1 || Filter2 || etc ist nicht möglich, da auch
// nicht gesetzte Filter hier keine Einschränkng bilden. Daher muss jede KAtegorie einzeln geprüft werden.
int total_filters = filters.status_count + filters.method_count + filters.ip_count +
filters.time_count + filters.user_agent_count + filters.url_count;
if (total_filters == 0) return 1;
int has_passing_filter = 0;
// Jede Filterkategorie wird nur geprüft, wenn auch entsprechende Filter gesetzt sind.
if (filters.status_count > 0) {
if (status_code_matches(all_entries[entry_index].status_code)) {
has_passing_filter = 1;
}
}
if (filters.method_count > 0) {
if (method_matches(all_entries[entry_index].request_method)) {
has_passing_filter = 1;
}
}
if (filters.ip_count > 0) {
if (ip_address_matches(all_entries[entry_index].ip_address)) {
has_passing_filter = 1;
}
}
if (filters.time_count > 0) {
if (time_matches(all_entries[entry_index].time)) {
has_passing_filter = 1;
}
}
if (filters.user_agent_count > 0) {
if (user_agent_matches(all_entries[entry_index].user_agent)) {
has_passing_filter = 1;
}
}
if (filters.url_count > 0) {
if (url_matches(all_entries[entry_index].url_path)) {
has_passing_filter = 1;
}
}
return has_passing_filter;
}
}
// 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;
}
// notwendig, um konformes Timestamp-Format aus simple_time struct zu generieren. Unterstützt derzeit nur UTC
void format_iso8601_time(struct simple_time time, char* buffer, int buffer_size) {
snprintf(buffer, buffer_size, "%04d-%02d-%02dT%02d:%02d:%02d+00:00",
time.year, time.month, time.day, time.hour, time.minute, time.second);
}
//Export in Timesketch-kompatiblem Format
void export_filtered_entries() {
printf("Dateiname für Timesketch-Export eingeben (ohne .csv): ");
//90 chars + delimiter
char filename[91];
if (scanf("%90s", filename) != 1) {
printf("FEHLER: Ungültiger Dateiname!\n");
clear_input_buffer();
return;
}
clear_input_buffer();
// Dateiendung
strcat(filename, ".csv");
FILE* file = fopen(filename, "w");
if (file == NULL) {
printf("ERROR: Kann Datei '%s' nicht erstellen!\n", filename);
return;
}
// CSV-Kopfzeile für Timesketch-Kompatibilität
fprintf(file, "datetime,timestamp_desc,ip_address,method,url_path,status_code,bytes_sent,user_agent,source_file,parsing_timestamp\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));
fprintf(file, "\"%s\",\"HTTP Access Log\",\"%s\",\"%s\",\"%s\",%d,%d,\"%s\",\"%s\",\"%s\"\n",
iso_datetime,
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].source_file,
all_entries[i].parsing_timestamp);
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_x_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("\nTOP %d IP-ADRESSEN\n", TOP_X);
printf("Rang | IP-Adresse | Anzahl Anfragen\n");
printf("-----|------------------|----------------\n");
int show_count = (unique_ips < TOP_X) ? unique_ips : TOP_X;
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++;
}
}
}
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("\nTOP %d USER AGENTS\n", TOP_X);
printf("Rang | User Agent | Anzahl Anfragen\n");
printf("-----|--------------------------------------|----------------\n");
int show_count = (unique_agents < TOP_X) ? unique_agents : TOP_X;
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 num_shown) {
int shown_count = 0;
printf("\nLOGDATEN:\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)) continue;
printf("%-16s | %-7s | %-22s | %-6d | %-5d | %-36s | %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++;
if (num_shown != 0 && shown_count >= num_shown) break; // für Preview: abbrechen wenn num_shown erreicht
}
if (num_shown != 0) {
printf("\nInsgesamt %d Einträge in der Vorschau.\n", shown_count);
} else {
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_status() {
printf("\nPREVIEW:\n");
show_filtered_entries(10);
printf("\nSTATUS\n");
if (total_entries > 0) {
printf(" %d Logzeilen in Datenstruktur\n", total_entries);
printf(" Speicherbelegung: %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry)));
} else {
printf(" ERROR: Keine Einträge in Datenstruktur!\n");
}
printf("\n Aktive Filter:\n");
int total_filters = filters.status_count + filters.method_count + filters.ip_count + filters.time_count + filters.user_agent_count + filters.url_count;
if (total_filters == 0) {
printf(" -> keine Filter gesetzt\n");
} else {
printf(" Modus: %s\n", filters.combination_mode == 0 ? "AND" : "OR");
printf(" Ausschlussfilter (!) haben Vorrang, dann Einschlussfilter\n");
printf("\n Gesetzt:\n");
if (filters.status_count > 0) {
printf(" -> Status: ");
int excludes = 0, includes = 0;
for (int i = 0; i < filters.status_count; i++) {
if (filters.status_filters[i].mode == FILTER_EXCLUDE) excludes++;
else includes++;
}
if (excludes > 0) {
printf("!(");
int first = 1;
for (int i = 0; i < filters.status_count; i++) {
if (filters.status_filters[i].mode == FILTER_EXCLUDE) {
if (!first) printf(" OR ");
printf("%d", filters.status_filters[i].code);
first = 0;
}
}
printf(")");
if (includes > 0) printf(" AND ");
}
if (includes > 0) {
if (includes > 1) printf("(");
int first = 1;
for (int i = 0; i < filters.status_count; i++) {
if (filters.status_filters[i].mode == FILTER_INCLUDE) {
if (!first) printf(" %s ", filters.combination_mode == 0 ? "AND" : "OR");
printf("%d", filters.status_filters[i].code);
first = 0;
}
}
if (includes > 1) printf(")");
}
printf("\n");
}
if (filters.method_count > 0) {
printf(" -> Request-Method: ");
int excludes = 0, includes = 0;
for (int i = 0; i < filters.method_count; i++) {
if (filters.method_filters[i].mode == FILTER_EXCLUDE) excludes++;
else includes++;
}
if (excludes > 0) {
printf("!(");
int first = 1;
for (int i = 0; i < filters.method_count; i++) {
if (filters.method_filters[i].mode == FILTER_EXCLUDE) {
if (!first) printf(" OR ");
printf("%s", filters.method_filters[i].pattern);
first = 0;
}
}
printf(")");
if (includes > 0) printf(" AND ");
}
if (includes > 0) {
if (includes > 1) printf("(");
int first = 1;
for (int i = 0; i < filters.method_count; i++) {
if (filters.method_filters[i].mode == FILTER_INCLUDE) {
if (!first) printf(" %s ", filters.combination_mode == 0 ? "AND" : "OR");
printf("%s", filters.method_filters[i].pattern);
first = 0;
}
}
if (includes > 1) printf(")");
}
printf("\n");
}
if (filters.ip_count > 0) {
printf(" -> IP-Adresse: ");
int excludes = 0, includes = 0;
for (int i = 0; i < filters.ip_count; i++) {
if (filters.ip_filters[i].mode == FILTER_EXCLUDE) excludes++;
else includes++;
}
if (excludes > 0) {
printf("!(");
int first = 1;
for (int i = 0; i < filters.ip_count; i++) {
if (filters.ip_filters[i].mode == FILTER_EXCLUDE) {
if (!first) printf(" OR ");
printf("%s", filters.ip_filters[i].ip_address);
first = 0;
}
}
printf(")");
if (includes > 0) printf(" AND ");
}
if (includes > 0) {
if (includes > 1) printf("(");
int first = 1;
for (int i = 0; i < filters.ip_count; i++) {
if (filters.ip_filters[i].mode == FILTER_INCLUDE) {
if (!first) printf(" %s ", filters.combination_mode == 0 ? "AND" : "OR");
printf("%s", filters.ip_filters[i].ip_address);
first = 0;
}
}
if (includes > 1) printf(")");
}
printf("\n");
}
if (filters.user_agent_count > 0) {
printf(" -> UserAgent: ");
int excludes = 0, includes = 0;
for (int i = 0; i < filters.user_agent_count; i++) {
if (filters.user_agent_filters[i].mode == FILTER_EXCLUDE) excludes++;
else includes++;
}
if (excludes > 0) {
printf("!(");
int first = 1;
for (int i = 0; i < filters.user_agent_count; i++) {
if (filters.user_agent_filters[i].mode == FILTER_EXCLUDE) {
if (!first) printf(" OR ");
printf("\"%s\"", filters.user_agent_filters[i].pattern);
first = 0;
}
}
printf(")");
if (includes > 0) printf(" AND ");
}
if (includes > 0) {
if (includes > 1) printf("(");
int first = 1;
for (int i = 0; i < filters.user_agent_count; i++) {
if (filters.user_agent_filters[i].mode == FILTER_INCLUDE) {
if (!first) printf(" %s ", filters.combination_mode == 0 ? "AND" : "OR");
printf("\"%s\"", filters.user_agent_filters[i].pattern);
first = 0;
}
}
if (includes > 1) printf(")");
}
printf("\n");
}
if (filters.url_count > 0) {
printf(" -> Payload/Pfad: ");
int excludes = 0, includes = 0;
for (int i = 0; i < filters.url_count; i++) {
if (filters.url_filters[i].mode == FILTER_EXCLUDE) excludes++;
else includes++;
}
if (excludes > 0) {
printf("!(");
int first = 1;
for (int i = 0; i < filters.url_count; i++) {
if (filters.url_filters[i].mode == FILTER_EXCLUDE) {
if (!first) printf(" OR ");
printf("\"%s\"", filters.url_filters[i].pattern);
first = 0;
}
}
printf(")");
if (includes > 0) printf(" AND ");
}
if (includes > 0) {
if (includes > 1) printf("(");
int first = 1;
for (int i = 0; i < filters.url_count; i++) {
if (filters.url_filters[i].mode == FILTER_INCLUDE) {
if (!first) printf(" %s ", filters.combination_mode == 0 ? "AND" : "OR");
printf("\"%s\"", filters.url_filters[i].pattern);
first = 0;
}
}
if (includes > 1) printf(")");
}
printf("\n");
}
if (filters.time_count > 0) {
printf(" -> Zeitraum: %d Filter gesetzt\n", filters.time_count);
}
int active_types = (filters.status_count > 0) + (filters.method_count > 0 ) + (filters.ip_count > 0) +
(filters.user_agent_count > 0) + (filters.time_count > 0) + (filters.url_count > 0);
if (active_types > 1) {
printf("\n%s-Verknüpfung (nur Einschlussfilter)\n",
filters.combination_mode == 0 ? "UND" : "ODER");
}
}
if (total_entries > 0) {
int filtered_count = count_filtered_entries();
printf("\n DATENSATZ: \n %d von %d Einträgen entsprechen den Filtern\n", filtered_count, total_entries);
}
}
void print_filter_examples() {
printf("\nFILTER-DOKUMENTATION\n");
printf("\nEXKLUSIONS-FILTER (immer OR-Logik, unabhängig vom Modus):\n");
printf("Beispiel: !('uptime' OR 'scanner')\n");
printf("> Schließt ALLE Einträge aus, die 'uptime' ODER 'scanner' enthalten\n\n");
printf("INKLUSIONS-FILTER im AND-Modus:\n");
printf("Beispiel: ('bot' AND 'crawl')\n");
printf("> Zeigt nur Einträge mit BEIDEN Begriffen\n\n");
printf("INKLUSIONS-FILTER im OR-Modus:\n");
printf("Beispiel: ('bot' OR 'crawl')\n");
printf("> Zeigt Einträge mit 'bot' ODER 'crawl'\n\n");
printf("KOMBINATION: Exklusion + Inklusion:\n");
printf("AND-Modus: !('uptime') AND ('bot' AND 'crawl')\n");
printf("OR-Modus: !('uptime') AND ('bot' OR 'crawl')\n\n");
printf("PRAKTISCHE ANWENDUNGSFÄLLE:\n");
printf("Malware-Erkennung:\n");
printf(" '.git' OR '.env' OR '/admin'\n");
printf(" > Verdächtige Pfad-Zugriffe\n\n");
printf("Bot-Traffic bereinigen:\n");
printf(" !('bot') AND Status=200 AND Method='GET'\n");
printf(" > Nur menschliche, erfolgreiche GET-Anfragen\n\n");
printf("Zeitraum-Analyse:\n");
printf(" Time='08:00-18:00' AND Status=500\n");
printf(" > Server-Fehler nur während Geschäftszeiten\n\n");
printf("DDoS-Verdacht:\n");
printf(" Status=429 OR Status=503 OR Status=500\n");
printf(" > Alle Überlastungs- und Fehler-Codes\n");
}
int handle_menu_shortcuts(int choice) {
if (choice == -2) {
return -2;
} else if (choice == -3) {
return -3;
} else if (choice == -4) {
printf("Programmende\n");
cleanup_memory();
exit(0);
}
return choice;
}
int safe_read_integer(const char* prompt, int min_val, int max_val) {
char input[50];
int value;
char *endptr;
while (1) {
printf("%s", prompt);
if (scanf("%49s", input) != 1) {
clear_input_buffer();
printf("FEHLER: Ungültige Eingabe. Bitte erneut versuchen.\n");
continue;
}
clear_input_buffer();
if (strcmp(input, "b") == 0 || strcmp(input, "B") == 0) return -2;
if (strcmp(input, "m") == 0 || strcmp(input, "M") == 0) return -3;
if (strcmp(input, "q") == 0 || strcmp(input, "Q") == 0) return -4;
value = strtol(input, &endptr, 10);
if (*endptr != '\0') {
printf("FEHLER: '%s' ist keine gültige Zahl. Bitte erneut versuchen.\n", input);
continue;
}
if (value < min_val || value > max_val) {
printf("FEHLER: Wert muss zwischen %d und %d liegen. Bitte erneut versuchen.\n", min_val, max_val);
continue;
}
return value;
}
}
int safe_read_string(const char* prompt, char* buffer, int buffer_size) {
while (1) {
printf("%s", prompt);
if (scanf("%s", buffer) != 1) {
clear_input_buffer();
printf("FEHLER: Ungültige Eingabe. Bitte erneut versuchen.\n");
continue;
}
clear_input_buffer();
if (strlen(buffer) >= buffer_size - 1) {
printf("FEHLER: Eingabe zu lang. Bitte erneut versuchen.\n");
continue;
}
if (strcmp(buffer, "b") == 0 || strcmp(buffer, "B") == 0) return -2;
if (strcmp(buffer, "m") == 0 || strcmp(buffer, "M") == 0) return -3;
if (strcmp(buffer, "q") == 0 || strcmp(buffer, "Q") == 0) return -4;
return 0;
}
}
int read_menu_input() {
char input[10];
if (scanf("%9s", input) != 1) {
clear_input_buffer();
return -1;
}
clear_input_buffer();
if (strcmp(input, "b") == 0 || strcmp(input, "B") == 0) return -2;
if (strcmp(input, "m") == 0 || strcmp(input, "M") == 0) return -3;
if (strcmp(input, "q") == 0 || strcmp(input, "Q") == 0) return -4;
char *endptr;
long number = strtol(input, &endptr, 10);
if (*endptr != '\0' || number < 0 || number > 999) {
return -1;
}
return (int)number;
}
void show_stats() {
int filtered_count = count_filtered_entries();
int count_200 = 0, count_300 = 0, count_400 = 0, count_500 = 0;
int count_get = 0, count_post = 0, count_atypical = 0, count_other = 0;
long total_bytes = 0;
int unique_ips = 0;
char ip_list[1000][50];
int ip_count = 0;
struct simple_time earliest_time = {31, 12, 9999, 23, 59, 59};
struct simple_time latest_time = {1, 1, 1970, 0, 0, 0};
for (int i = 0; i < total_entries; i++) {
if (passes_filter(i)) {
if (all_entries[i].status_code >= 200 && all_entries[i].status_code < 300) count_200++;
else if (all_entries[i].status_code >= 300 && all_entries[i].status_code < 400) count_300++;
else if (all_entries[i].status_code >= 400 && all_entries[i].status_code < 500) count_400++;
else if (all_entries[i].status_code >= 500) count_500++;
if (strcmp(all_entries[i].request_method, "GET") == 0) count_get++;
else if (strcmp(all_entries[i].request_method, "POST") == 0) count_post++;
else if (strcmp(all_entries[i].request_method, "ATYPICAL") == 0) count_atypical++;
else count_other++;
total_bytes += all_entries[i].bytes_sent;
int ip_exists = 0;
for (int j = 0; j < ip_count; j++) {
if (strcmp(ip_list[j], all_entries[i].ip_address) == 0) {
ip_exists = 1;
break;
}
}
if (!ip_exists && ip_count < 1000) {
strcpy(ip_list[ip_count], all_entries[i].ip_address);
ip_count++;
}
if (compare_times(all_entries[i].time, earliest_time) < 0) {
earliest_time = all_entries[i].time;
}
if (compare_times(all_entries[i].time, latest_time) > 0) {
latest_time = all_entries[i].time;
}
}
}
unique_ips = ip_count;
printf("\nDATENANALYSE\n");
printf(" Gesamte Einträge: %d\n Gefilterte Einträge: %d (%.1f%%)\n Eindeutige IPs: %d\n",
total_entries, filtered_count,
total_entries > 0 ? (filtered_count * 100.0 / total_entries) : 0.0, unique_ips);
if (filtered_count > 0) {
printf(" Ø Anfragen pro IP: %.1f\n", (double)filtered_count / unique_ips);
printf(" Zeitspanne: %02d.%02d.%d %02d:%02d:%02d - %02d.%02d.%d %02d:%02d:%02d\n",
earliest_time.day, earliest_time.month, earliest_time.year,
earliest_time.hour, earliest_time.minute, earliest_time.second,
latest_time.day, latest_time.month, latest_time.year,
latest_time.hour, latest_time.minute, latest_time.second);
int total_minutes = (latest_time.year - earliest_time.year) * 525600 +
(latest_time.month - earliest_time.month) * 43800 +
(latest_time.day - earliest_time.day) * 1440 +
(latest_time.hour - earliest_time.hour) * 60 +
(latest_time.minute - earliest_time.minute);
if (total_minutes > 0) {
printf(" Ø Anfragen/Minute: %.2f", (double)filtered_count / total_minutes);
if (total_minutes >= 60) {
printf("\n Ø Anfragen/Stunde: %.2f", (double)filtered_count * 60 / total_minutes);
}
printf("\n");
}
printf(" Status-Codes:\n 2xx:%d (%.1f%%)\n 3xx:%d (%.1f%%)\n 4xx:%d (%.1f%%)\n 5xx:%d (%.1f%%)\n",
count_200, count_200 * 100.0 / filtered_count,
count_300, count_300 * 100.0 / filtered_count,
count_400, count_400 * 100.0 / filtered_count,
count_500, count_500 * 100.0 / filtered_count);
printf(" HTTP-Methoden:\n GET:%d (%.1f%%)\n POST:%d (%.1f%%)\n Andere:%d (%.1f%%)\n",
count_get, count_get * 100.0 / filtered_count,
count_post, count_post * 100.0 / filtered_count,
count_other, count_other * 100.0 / filtered_count);
printf(" Datenumfang:\n %ld Bytes total\n Ø %.1f Bytes/Anfrage\n Fehlerrate: %.1f%%\n",
total_bytes, (double)total_bytes / filtered_count,
((count_400 + count_500) * 100.0) / filtered_count);
}
}
void show_main_menu() {
printf("\nHAUPTMENÜ\n");
printf("1. Filter verwalten\n");
printf("2. Daten anzeigen und exportieren\n");
printf("3. Statistiken anzeigen\n");
printf("4. Programm beenden\n");
printf("Navigation: [b]Zurück [m]Hauptmenü [q]Beenden\n");
printf("Auswahl: ");
}
int menu_set_filters() {
int choice = 0;
while (choice != -2 && choice != -3) {
show_status();
printf("\nFILTER HINZUFÜGEN\n");
printf("1. Status-Code (exakte Suche)\n");
printf("2. IP-Adresse (exakte Suche)\n");
printf("3. Zeitraum (Start- und Endzeit)\n");
printf("4. User-Agent (Teilstring-Suche)\n");
printf("5. HTTP-Methode (Teilstring-Suche)\n");
printf("6. URL-Pfad (Teilstring-Suche)\n");
printf("Navigation: [b]Zurück [m]Hauptmenü [q]Beenden\n");
printf("Auswahl: ");
choice = read_menu_input();
choice = handle_menu_shortcuts(choice);
if (choice == 1) {
if (filters.status_count >= MAX_FILTERS) {
printf("FEHLER: Maximale Anzahl Status-Code Filter erreicht (%d)!\n", MAX_FILTERS);
continue;
}
int status = safe_read_integer("Status Code eingeben (z.B. 200, 404, 500): ", 100, 599);
if (status < 0) continue;
printf("\nFilter-Typ wählen:\n");
printf("1. Einschließen (nur Status %d anzeigen)\n", status);
printf("2. Ausschließen (Status %d NICHT anzeigen)\n", status);
int filter_type = safe_read_integer("Auswahl: ", 1, 2);
if (filter_type < 0) continue;
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. Gesamt: %d\n", filters.status_count);
} else if (choice == 2) {
if (filters.ip_count >= MAX_FILTERS) {
printf("FEHLER: Maximale Anzahl IP-Filter erreicht (%d)!\n", MAX_FILTERS);
continue;
}
char ip[50];
int result = safe_read_string("IP-Adresse eingeben: ", ip, sizeof(ip));
if (result < 0) continue;
printf("\nFilter-Typ wählen:\n");
printf("1. Einschließen (nur IP %s anzeigen)\n", ip);
printf("2. Ausschließen (IP %s NICHT anzeigen)\n", ip);
int filter_type = safe_read_integer("Auswahl: ", 1, 2);
if (filter_type < 0) continue;
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. Gesamt: %d\n", filters.ip_count);
} else if (choice == 3) {
if (filters.time_count >= MAX_FILTERS) {
printf("FEHLER: Maximale Anzahl Zeitraum-Filter erreicht (%d)!\n", MAX_FILTERS);
continue;
}
printf("\nZEITRAUM FILTER HINZUFÜGEN\n");
struct time_filter new_time_filter = {0};
printf("STARTZEIT:\n");
int start_year = safe_read_integer("Jahr (z.B. 2023): ", 1970, 2100);
if (start_year < 0) continue;
int start_month = safe_read_integer("Monat (1-12): ", 1, 12);
if (start_month < 0) continue;
int start_day = safe_read_integer("Tag (1-31): ", 1, 31);
if (start_day < 0) continue;
int start_hour = safe_read_integer("Stunde (0-23): ", 0, 23);
if (start_hour < 0) continue;
int start_minute = safe_read_integer("Minute (0-59): ", 0, 59);
if (start_minute < 0) continue;
int start_second = safe_read_integer("Sekunde (0-59): ", 0, 59);
if (start_second < 0) continue;
printf("\nENDZEIT:\n");
int end_year = safe_read_integer("Jahr (z.B. 2023): ", 1970, 2100);
if (end_year < 0) continue;
int end_month = safe_read_integer("Monat (1-12): ", 1, 12);
if (end_month < 0) continue;
int end_day = safe_read_integer("Tag (1-31): ", 1, 31);
if (end_day < 0) continue;
int end_hour = safe_read_integer("Stunde (0-23): ", 0, 23);
if (end_hour < 0) continue;
int end_minute = safe_read_integer("Minute (0-59): ", 0, 59);
if (end_minute < 0) continue;
int end_second = safe_read_integer("Sekunde (0-59): ", 0, 59);
if (end_second < 0) continue;
printf("\nFilter-Typ wählen:\n");
printf("1. Einschließen (nur Ereignisse IN diesem Zeitraum)\n");
printf("2. Ausschließen (Ereignisse in diesem Zeitraum NICHT anzeigen)\n");
int filter_type = safe_read_integer("Auswahl: ", 1, 2);
if (filter_type < 0) 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. Gesamt: %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;
}
char pattern[256];
int result = safe_read_string("User-Agent Suchtext eingeben (z.B. 'chrome', 'bot', 'scanner'): ", pattern, sizeof(pattern));
if (result < 0) continue;
printf("\nFilter-Typ wählen:\n");
printf("1. Einschließen (nur User-Agents mit '%s')\n", pattern);
printf("2. Ausschließen (User-Agents mit '%s' NICHT anzeigen)\n", pattern);
int filter_type = safe_read_integer("Auswahl: ", 1, 2);
if (filter_type < 0) continue;
strcpy(filters.user_agent_filters[filters.user_agent_count].pattern, pattern);
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. Gesamt: %d\n", filters.user_agent_count);
} else if (choice == 5) {
if (filters.method_count >= MAX_FILTERS) {
printf("FEHLER: Maximale Anzahl Method-Filter erreicht (%d)!\n", MAX_FILTERS);
continue;
}
char pattern[10];
int result = safe_read_string("HTTP-Methode eingeben (z.B. 'GET', 'POST', 'PUT', Sonderwert: 'ATYPICAL'): ", pattern, sizeof(pattern));
if (result < 0) continue;
printf("\nFilter-Typ wählen:\n");
printf("1. Einschließen (nur Methode '%s')\n", pattern);
printf("2. Ausschließen (Methode '%s' NICHT anzeigen)\n", pattern);
int filter_type = safe_read_integer("Auswahl: ", 1, 2);
if (filter_type < 0) continue;
strcpy(filters.method_filters[filters.method_count].pattern, pattern);
filters.method_filters[filters.method_count].mode = (filter_type == 2) ? FILTER_EXCLUDE : FILTER_INCLUDE;
filters.method_count++;
printf("Method-Filter hinzugefügt. Gesamt: %d\n", filters.method_count);
} else if (choice == 6) {
if (filters.url_count >= MAX_FILTERS) {
printf("FEHLER: Maximale Anzahl URL-Filter erreicht (%d)!\n", MAX_FILTERS);
continue;
}
char pattern[MAX_REQUEST_LENGTH];
int result = safe_read_string("URL-Pfad Suchtext eingeben (z.B. '.git', '.php', '/admin', 'wp-'): ", pattern, sizeof(pattern));
if (result < 0) continue;
printf("\nFilter-Typ wählen:\n");
printf("1. Einschließen (nur URLs mit '%s')\n", pattern);
printf("2. Ausschließen (URLs mit '%s' NICHT anzeigen)\n", pattern);
int filter_type = safe_read_integer("Auswahl: ", 1, 2);
if (filter_type < 0) continue;
strcpy(filters.url_filters[filters.url_count].pattern, pattern);
filters.url_filters[filters.url_count].mode = (filter_type == 2) ? FILTER_EXCLUDE : FILTER_INCLUDE;
filters.url_count++;
printf("URL-Filter hinzugefügt. Gesamt: %d\n", filters.url_count);
} else if (choice == -2) {
return -2;
} else if (choice == -3) {
return -3;
} else if (choice != -1) {
printf("FEHLER: Ungültige Auswahl! Bitte wählen Sie 1-6 oder b/m/q.\n");
}
}
return choice;
}
void menu_delete_filters() {
int total_filters = filters.status_count + filters.ip_count + filters.time_count +
filters.user_agent_count + filters.method_count + filters.url_count;
if (total_filters == 0) {
printf("Keine Filter gesetzt zum Löschen.\n");
return;
}
printf("\nFILTER LÖSCHEN\n");
printf("Aktuelle Filter:\n\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("%2d. Status-Code: %d (%s)\n", filter_index++, filters.status_filters[i].code, mode_str);
}
for (int i = 0; i < filters.method_count; i++) {
char* mode_str = (filters.method_filters[i].mode == FILTER_EXCLUDE) ? "ausschließen" : "einschließen";
printf("%2d. HTTP-Methode: \"%s\" (%s)\n", filter_index++, filters.method_filters[i].pattern, 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("%2d. 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("%2d. User-Agent: \"%s\" (%s)\n", filter_index++, filters.user_agent_filters[i].pattern, mode_str);
}
for (int i = 0; i < filters.url_count; i++) {
char* mode_str = (filters.url_filters[i].mode == FILTER_EXCLUDE) ? "ausschließen" : "einschließen";
printf("%2d. URL-Pfad: \"%s\" (%s)\n", filter_index++, filters.url_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("%2d. 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);
}
int choice = safe_read_integer("Filter zum Löschen auswählen (1-%d) oder 0 für Abbrechen: ", 0, total_filters);
if (choice < 0) return;
if (choice == 0) {
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.method_count; i++) {
if (current_index == choice) {
for (int j = i; j < filters.method_count - 1; j++) {
filters.method_filters[j] = filters.method_filters[j + 1];
}
filters.method_count--;
printf("Method-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.url_count; i++) {
if (current_index == choice) {
for (int j = i; j < filters.url_count - 1; j++) {
filters.url_filters[j] = filters.url_filters[j + 1];
}
filters.url_count--;
printf("URL-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("\nFILTER-MODUS ÄNDERN\n");
printf("Aktueller Modus: %s\n\n", filters.combination_mode == 0 ? "AND (alle müssen zutreffen)" : "OR (einer muss zutreffen)");
printf("HINWEIS: Der Modus gilt nur für Einschluss-Filter der gleichen Kategorie.\n");
printf("Ausschluss-Filter arbeiten immer im OR-Modus und haben Vorrang.\n\n");
printf("1. AND-Modus (alle Einschluss-Filter müssen zutreffen)\n");
printf("2. OR-Modus (mindestens ein Einschluss-Filter muss zutreffen)\n");
printf("3. Detaillierte Beispiele anzeigen\n");
printf("Navigation: [b]Zurück [m]Hauptmenü [q]Beenden\n");
printf("Auswahl: ");
int choice = read_menu_input();
choice = handle_menu_shortcuts(choice);
if (choice == 1) {
filters.combination_mode = 0;
printf("Filter-Modus auf AND gesetzt.\n");
} else if (choice == 2) {
filters.combination_mode = 1;
printf("Filter-Modus auf OR gesetzt.\n");
} else if (choice == 3) {
print_filter_examples();
} else if (choice == -2 || choice == -3) {
return;
} else if (choice != -1) {
printf("FEHLER: Ungültige Auswahl! Bitte wählen Sie 1-3 oder b/m/q.\n");
}
}
void menu_reset_filters() {
printf("\nFILTER ZURÜCKSETZEN\n");
int total_filters = filters.status_count + filters.method_count + filters.ip_count +
filters.time_count + filters.user_agent_count + filters.url_count;
if (total_filters == 0) {
printf("Keine Filter gesetzt zum Zurücksetzen.\n");
return;
}
printf("WARNUNG: Alle %d Filter werden gelöscht!\n\n", total_filters);
printf("1. Ja, alle Filter löschen\n");
printf("2. Abbrechen\n");
printf("Navigation: [b]Zurück [m]Hauptmenü [q]Beenden\n");
printf("Auswahl: ");
int choice = read_menu_input();
choice = handle_menu_shortcuts(choice);
if (choice == 1) {
filters.status_count = 0;
filters.method_count = 0;
filters.ip_count = 0;
filters.time_count = 0;
filters.user_agent_count = 0;
filters.url_count = 0;
filters.combination_mode = 0;
printf("Alle Filter zurückgesetzt.\n");
} else if (choice == 2) {
printf("Zurücksetzen abgebrochen.\n");
} else if (choice == -2 || choice == -3) {
return;
} else if (choice != -1) {
printf("FEHLER: Ungültige Auswahl! Bitte wählen Sie 1-2 oder b/m/q.\n");
}
}
void menu_filter_management() {
int choice = 0;
while (choice != -2 && choice != -3) {
show_status();
printf("\nFILTER VERWALTEN\n");
printf("1. Filter hinzufügen\n");
printf("2. Filter löschen\n");
printf("3. Filter-Modus ändern (AND/OR)\n");
printf("4. Alle Filter zurücksetzen\n");
printf("5. Filter-Dokumentation anzeigen\n");
printf("Navigation: [b]Zurück [m]Hauptmenü [q]Beenden\n");
printf("Auswahl: ");
choice = read_menu_input();
choice = handle_menu_shortcuts(choice);
if (choice == 1) {
int sub_result = menu_set_filters();
if (sub_result == -3) return;
} 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) {
print_filter_examples();
} else if (choice == -2) {
return;
} else if (choice == -3) {
return;
} else if (choice != -1) {
printf("FEHLER: Ungültige Auswahl! Bitte wählen Sie 1-5 oder b/m/q.\n");
}
}
}
void menu_show_entries() {
int choice = 0;
int supress_preview = 0;
while (choice != -2 && choice != -3) {
if (supress_preview == 0) {
show_status();
supress_preview = 0;
}
int filtered_count = count_filtered_entries();
printf("\nDATEN ANZEIGEN UND EXPORTIEREN\n");
printf("Verfügbare Einträge: %d (gefiltert von %d)\n\n", filtered_count, total_entries);
printf("1. Alle gefilterten Einträge anzeigen\n");
printf("2. Als CSV exportieren (Timesketch-kompatibel)\n");
printf("3. Top %d IP-Adressen anzeigen\n", TOP_X);
printf("4. Top %d User-Agents anzeigen\n", TOP_X);
printf("Navigation: [b]Zurück [m]Hauptmenü [q]Beenden\n");
printf("Auswahl: ");
choice = read_menu_input();
choice = handle_menu_shortcuts(choice);
if (choice == 1) {
if (filtered_count > 1000) {
printf("\nWARNUNG: Die Anzeige von %d Einträgen in der Kommandozeile ist unübersichtlich.\n", filtered_count);
printf("Empfehlung: Verwenden Sie den CSV-Export für große Datenmengen.\n\n");
printf("1. Trotzdem anzeigen\n");
printf("2. Abbrechen\n");
int confirm = safe_read_integer("Auswahl: ", 1, 2);
if (confirm != 1) {
printf("Anzeige abgebrochen.\n");
continue;
}
}
supress_preview = 1;
show_filtered_entries(0);
} else if (choice == 2) {
export_filtered_entries();
} else if (choice == 3) {
show_top_x_ips();
} else if (choice == 4) {
show_top_user_agents();
} else if (choice == -2) {
return;
} else if (choice == -3) {
return;
} else if (choice != -1) {
printf("FEHLER: Ungültige Auswahl! Bitte wählen Sie 1-4 oder b/m/q.\n");
}
}
}
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;
} else if (argc == 3) {
}
printf("\nNGINX EXAMINATOR\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;
int stats_show = 0;
while (choice != 4 && choice != -4) {
show_status();
if (stats_show == 1) {
show_stats();
int stats_show = 0;
}
show_main_menu();
choice = read_menu_input();
choice = handle_menu_shortcuts(choice);
if (choice == -1) {
printf("FEHLER: Bitte geben Sie eine gültige Zahl oder Navigation ein (1-4, b, m, q)!\n");
continue;
}
if (choice == 1) {
menu_filter_management();
} else if (choice == 2) {
menu_show_entries();
} else if (choice == 3) {
stats_show =1;
} else if (choice == 4) {
printf("Programmende\n");
break;
} else if (choice == -4) {
break;
} else {
printf("FEHLER: Ungültige Auswahl! Bitte wählen Sie 1-4 oder b/m/q.\n");
}
}
cleanup_memory();
return 0;
}