This commit is contained in:
overcuriousity 2025-09-02 23:40:35 +02:00
parent 88e948825f
commit 291c72c2a5

View File

@ -126,6 +126,13 @@ int is_space(char c) {
return (c == ' ' || c == '\t');
}
//Hilfsfunktion zum Prüfen, ob ein String mit einem bestimmten Präfix beginnt. Nötig für das Parsen der Filter in der main()-Funktion
int starts_with(char* str, char* prefix) {
//positiver Vergleich gibt bei strncmp 0 aus - daher prüfen ob == 0, um 1 bei positiver Prüfung zu erhalten
// strncmp statt strcmp, da nur die ersten Bytes beider Strings geprüft werden (Anzahl: drittes Argument)
return strncmp(str, prefix, strlen(prefix)) == 0;
}
// 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)) {
@ -230,8 +237,8 @@ void mem_expand_dynamically() {
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)));
printf("FEHLER: Speicher konnte nicht auf %d Einträge erweitert werden, ..\n", max_entries);
printf("FEHLER: Benötigter Speicher: %lu Bytes\n", (unsigned long)(max_entries * sizeof(struct log_entry)));
cleanup_and_exit();
}
@ -245,8 +252,8 @@ void allocate_initial_memory() {
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)));
printf("FEHLER: Konnte %d Einträge nicht allozieren, ..\n", max_entries);
printf("FEHLER: %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
}
@ -397,7 +404,7 @@ int parse_simple_log_line(char* line, int entry_index, char* source_file) { // 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 des Timestamps aufgetreten, dieser sollte folgendes Format haben:\n[DD/MMM/YYYY:HH:MM:SS +0000]\n\n");
printf("FEHLER: 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();
}
@ -472,7 +479,7 @@ int parse_simple_log_line(char* line, int entry_index, char* source_file) { // 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");
printf("FEHLER: 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();
}
@ -506,7 +513,7 @@ int parse_simple_log_line(char* line, int entry_index, char* source_file) { // N
}
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");
printf("FEHLER: Unerwartetes Log-Format. Lediglich mit standard-nginx-accesslog kompatibel.\nDer Fehler ist beim Prüfen des Referrer-Feldes aufgetreten.\n\n");
cleanup_and_exit();
}
@ -523,7 +530,7 @@ int parse_simple_log_line(char* line, int entry_index, char* source_file) { // N
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");
printf("FEHLER: 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));
@ -537,7 +544,7 @@ int parse_simple_log_line(char* line, int entry_index, char* source_file) { // N
void load_regular_file(char* filename) {
FILE* file = fopen(filename, "r");
if (file == NULL) {
printf("ERROR: Kann Datei '%s' nicht öffnen!\n", filename);
printf("FEHLER: Kann Datei '%s' nicht öffnen!\n", filename);
return;
}
printf("INFO: Lade Datei: %s\n", filename);
@ -592,7 +599,7 @@ void load_log_file(char* path) {
closedir(dir);
if (files_found == 0) {
printf("WARNING: Keine .log Dateien im Verzeichnis gefunden.\n");
printf("WARNUNG: Keine .log Dateien im Verzeichnis gefunden.\n");
printf(" Tipp: Für .gz Dateien verwenden Sie 'gunzip *.gz' zum Dekomprimieren\n");
} else {
printf("INFO: Insgesamt %d .log Dateien verarbeitet.\n", files_found);
@ -1000,7 +1007,7 @@ void export_filtered_entries(char *filepath) {
printf("\nINFO: Schreibe Datei %s...\n", filename);
FILE* file = fopen(filename, "w");
if (file == NULL) {
printf("ERROR: Kann Datei '%s' nicht erstellen!\n", filename);
printf("FEHLER: Kann Datei '%s' nicht erstellen!\n", filename);
return;
}
@ -1202,7 +1209,7 @@ void show_status() {
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(" FEHLER: Keine Einträge in Datenstruktur!\n");
}
printf("\n Aktive Filter:\n");
@ -1472,30 +1479,34 @@ int handle_menu_shortcuts(int choice) {
return choice;
}
// überall wo integer aus String-Input gelesen werden müssen. basiert auf strtol, was gegen Buffer Overflow sicher sein soll
int safe_read_integer(const char* prompt, int min_val, int max_val) {
char input[50];
int value;
char *endptr;
// endlos
while (1) {
printf("%s", prompt);
// scanf liest den Input in einen pointer ein, daher nicht &input. Die Usereingabe ist ein String, also ein Array
if (scanf("%49s", input) != 1) {
clear_input_buffer();
printf("FEHLER: Ungültige Eingabe. Bitte erneut versuchen.\n");
continue;
}
clear_input_buffer();
// Standard Rückgabewerte für die Menünavigation, die Werte werden für die choice-Variable genutzt
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;
// Konvertierung der Eingabe in einen Long-Integer der Basis 10. Der endptr speichert einen Pointer auf das erste ungültige Zeichen nach einlesen des Lon-Integer
value = strtol(input, &endptr, 10);
// wenn der endptr der Nullterminator ist, handelte es sich bei der Eingabe sicher um einen Long-Integer.
if (*endptr != '\0') {
printf("FEHLER: '%s' ist keine gültige Zahl. Bitte erneut versuchen.\n", input);
continue;
}
// Prüfen, ob sich der Wert im Erwartungsbereich befindet
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;
@ -2169,6 +2180,227 @@ void menu_show_entries() {
}
}
// Funktion zum Parsen der Filter-Werte, die mit --<Filtertyp>=<Werte,Komma-getrennt> übergeben werden.
// values_str sind die Werte hinter dem =, filter_type die Werte vor dem =
// filter_type wird von parse_filter_argument() übergeben
void parse_filter_values(const char* values_str, const char* filter_type) {
char values_local[1024];
// Werte in lokale Variable einlesen, Nullterminator setzen
strncpy(values_local, values_str, sizeof(values_local) - 1);
values_local[sizeof(values_local) - 1] = '\0';
// Einlesen der Werte mit strtok. Hier wird das erste Token gesetzt, damit die exit condition der while-Schleife nicht triggert.
// strtok liest einen string in einen Pointer bis zu einem spezifizierten Delimiter ein. Die gelesenen Bytes werden aus dem Input entfernt.
char* token = strtok(values_local, ",");
while (token != NULL) {
// Sollte der Nutzer Leerzeichen verwendet haben, müssen diese übersprungen werden
while (*token == ' ') token++;
// setzen des Modus, Standard inklusiv
filter_mode_t mode = FILTER_INCLUDE;
// ..mit !-Präfix exklusiv.
if (*token == '!') {
mode = FILTER_EXCLUDE;
token++; // den Pointer vom ! weiteriterieren, dieser ist nicht Teil des Filters
}
if (strlen(token) > 0) {
// wenn das token Werte hat, werden die entsprechenden Funktionen aufgerufen, die die Filter setzen
if (strcmp(filter_type, "status") == 0) {
add_status_filter(token, mode);
} else if (strcmp(filter_type, "ip") == 0) {
add_ip_filter(token, mode);
} else if (strcmp(filter_type, "method") == 0) {
add_method_filter(token, mode);
} else if (strcmp(filter_type, "useragent") == 0) {
add_useragent_filter(token, mode);
} else if (strcmp(filter_type, "url") == 0) {
add_url_filter(token, mode);
}
}
// nächste Iteration - nächstes token einlesen, damit die while-Schleife weiteriteriert
token = strtok(NULL, ",");
}
}
// Funktionen zum setzen der Filter (existierende Datenstrukturen)
void add_status_filter(const char* value, filter_mode_t mode) {
if (filters.status_count >= MAX_FILTERS) {
printf("WARNUNG: MAX_FILTERS überschritten, ignoriere: %s\n", value);
return;
}
// TODO
int status_code = atoi(value);
if (status_code < 100 || status_code > 599) {
printf("WARNUNG: Invalid status code: %s (must be 100-599)\n", value);
return;
}
filters.status_filters[filters.status_count].code = status_code;
filters.status_filters[filters.status_count].mode = mode;
filters.status_count++;
printf("Added status filter: %s%d\n", mode == FILTER_EXCLUDE ? "!" : "", status_code);
}
void add_ip_filter(const char* value, filter_mode_t mode) {
if (filters.ip_count >= MAX_FILTERS) {
printf("WARNUNG: MAX_FILTERS überschritten, ignoriere: %s\n", value);
return;
}
if (strlen(value) >= sizeof(filters.ip_filters[0].ip_address)) {
printf("WARNUNG: IP address too long: %s\n", value);
return;
}
strcpy(filters.ip_filters[filters.ip_count].ip_address, value);
filters.ip_filters[filters.ip_count].mode = mode;
filters.ip_count++;
printf("Added IP filter: %s%s\n", mode == FILTER_EXCLUDE ? "!" : "", value);
}
void add_method_filter(const char* value, filter_mode_t mode) {
if (filters.method_count >= MAX_FILTERS) {
printf("WARNUNG: MAX_FILTERS überschritten, ignoriere: %s\n", value);
return;
}
if (strlen(value) >= sizeof(filters.method_filters[0].pattern)) {
printf("WARNUNG: Method pattern too long: %s\n", value);
return;
}
strcpy(filters.method_filters[filters.method_count].pattern, value);
filters.method_filters[filters.method_count].mode = mode;
filters.method_count++;
printf("Added method filter: %s%s\n", mode == FILTER_EXCLUDE ? "!" : "", value);
}
void add_useragent_filter(const char* value, filter_mode_t mode) {
if (filters.user_agent_count >= MAX_FILTERS) {
printf("WARNUNG: MAX_FILTERS überschritten, ignoriere: %s\n", value);
return;
}
if (strlen(value) >= sizeof(filters.user_agent_filters[0].pattern)) {
printf("WARNUNG: User agent pattern too long: %s\n", value);
return;
}
strcpy(filters.user_agent_filters[filters.user_agent_count].pattern, value);
filters.user_agent_filters[filters.user_agent_count].mode = mode;
filters.user_agent_count++;
printf("Added user agent filter: %s%s\n", mode == FILTER_EXCLUDE ? "!" : "", value);
}
void add_url_filter(const char* value, filter_mode_t mode) {
if (filters.url_count >= MAX_FILTERS) {
printf("WARNUNG: MAX_FILTERS überschritten, ignoriere: %s\n", value);
return;
}
if (strlen(value) >= sizeof(filters.url_filters[0].pattern)) {
printf("WARNUNG: URL pattern too long: %s\n", value);
return;
}
strcpy(filters.url_filters[filters.url_count].pattern, value);
filters.url_filters[filters.url_count].mode = mode;
filters.url_count++;
printf("Added URL filter: %s%s\n", mode == FILTER_EXCLUDE ? "!" : "", value);
}
// Main filter parsing function
int parse_filter_argument(const char* arg) {
if (!starts_with(arg, "--")) {
return 0; // Not a filter argument
}
// Find the '=' character
const char* equals_pos = strchr(arg, '=');
if (equals_pos == NULL) {
printf("WARNUNG: Invalid filter format (missing =): %s\n", arg);
return 0;
}
// Extract filter type (between -- and =)
int type_len = equals_pos - arg - 2; // -2 for the "--"
char filter_type[50];
strncpy(filter_type, arg + 2, type_len);
filter_type[type_len] = '\0';
// Extract values (after =)
const char* values = equals_pos + 1;
// Parse based on filter type
if (strcmp(filter_type, "status") == 0) {
parse_filter_values(values, add_status_filter);
} else if (strcmp(filter_type, "ip") == 0) {
parse_filter_values(values, add_ip_filter);
} else if (strcmp(filter_type, "method") == 0) {
parse_filter_values(values, add_method_filter);
} else if (strcmp(filter_type, "useragent") == 0) {
parse_filter_values(values, add_useragent_filter);
} else if (strcmp(filter_type, "url") == 0) {
parse_filter_values(values, add_url_filter);
} else if (strcmp(filter_type, "mode") == 0) {
if (strcmp(values, "and") == 0 || strcmp(values, "AND") == 0) {
filters.combination_mode = 0;
printf("Set filter combination mode: AND\n");
} else if (strcmp(values, "or") == 0 || strcmp(values, "OR") == 0) {
filters.combination_mode = 1;
printf("Set filter combination mode: OR\n");
} else {
printf("WARNUNG: Invalid mode value: %s (use 'and' or 'or')\n", values);
}
} else {
printf("WARNUNG: Unknown filter type: %s\n", filter_type);
return 0;
}
return 1; // Successfully parsed
}
// Add this to your main() function after allocate_initial_memory()
void parse_command_line_filters(int argc, char* argv[]) {
int has_filter_flag = 0;
// First, check if -f flag is present
for (int i = 2; i < argc; i++) {
if (strcmp(argv[i], "-f") == 0) {
has_filter_flag = 1;
break;
}
}
if (!has_filter_flag) {
return; // No filter flag, skip filter parsing
}
printf("Parsing command line filters...\n");
// Parse all filter arguments
for (int i = 2; i < argc; i++) {
if (starts_with(argv[i], "--")) {
parse_filter_argument(argv[i]);
}
}
// Show summary of parsed filters
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("Successfully parsed %d filters from command line.\n", total_filters);
} else {
printf("No valid filters found in command line arguments.\n");
}
}
void print_help(char* binary) {
printf("\nNGINX EXAMINATOR\n");
printf("Verwendung:\n");
@ -2220,16 +2452,48 @@ void print_help(char* binary) {
int main(int argc, char* argv[]) {
if (argc <= 3) {
if (argc < 3) {
print_help(argv[0]);
return 1;
}
printf("\nNGINX EXAMINATOR\n");
char* input_path = argv[1];
int flag_interactive = 0;
int flag_export = 0;
int flag_filters = 0;
int flag_help = 0;
char export_filename[90];
int flag_has_filename = 0;
allocate_initial_memory();
if (strcmp(argv[2], "-i") == 0) {
if (argc >= 3){
for (int i=2; i<argc; i++) {
if (strcmp(argv[i], "-i")==0){
flag_interactive = 1;
} else if (strcmp(argv[i], "-e")==0) {
flag_export = 1;
if (i+1<argc && argv[i+1][0]!='-'){
strncpy(export_filename, argv[i + 1], sizeof(export_filename) - 1);
flag_has_filename = 1;
i++; // Schleife weiter iterieren
}
} else if (strcmp(argv[i], "--filters")==0) {
flag_filters = 1;
} else if (strcmp(argv[i], "-h")==0) {
flag_help = 1;
}
}
}
if (flag_export == 1 && flag_interactive == 1) {
printf("FEHLER: Interaktiver Modus kann nicht mit Export-Modus kombiniert werden. Mit %c ausführen für Anleitung.", argv[0]);
return 1;
}else if (flag_interactive == 1) {
load_log_file(argv[1]);
if (total_entries == 0) {
@ -2270,14 +2534,14 @@ int main(int argc, char* argv[]) {
printf("FEHLER: Ungültige Auswahl! Bitte wählen Sie 1-4 oder b/m/q.\n");
}
}
} else if (strcmp(argv[2], "-e") == 0){
} else if (flag_export == 1){
load_log_file(argv[1]);
if (argc == 4) {
export_filtered_entries(argv[3]);
if (flag_has_filename == 1) {
export_filtered_entries(export_filename);
} else {
export_filtered_entries(NULL);
}
} else if (strcmp(argv[2], "-h") == 0){
} else if (flag_help == 1){
print_help(argv[0]);
return 1;
}