diff --git a/.gitignore b/.gitignore index d7680e2..f766f6e 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,10 @@ test_truerng # Temporary files *.pad *.state + +# Downloaded dependencies (source) +miniz/ +microtar/ + +# Test directories +test_dir/ diff --git a/Makefile b/Makefile index af5c641..b8b672d 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,10 @@ CC = gcc -CFLAGS = -Wall -Wextra -std=c99 -Isrc +CFLAGS = -Wall -Wextra -std=c99 -Isrc -Isrc/miniz -Isrc/microtar LIBS = -lm LIBS_STATIC = -static -lm ARCH = $(shell uname -m) TARGET = build/otp-$(ARCH) -SOURCES = $(wildcard src/*.c) +SOURCES = $(wildcard src/*.c) $(wildcard src/miniz/*.c) $(wildcard src/microtar/*.c) OBJS = $(SOURCES:.c=.o) # Default build target diff --git a/README.md b/README.md index fdaf5de..619c994 100644 --- a/README.md +++ b/README.md @@ -58,14 +58,14 @@ One-time pads can be trivially encrypted and decrypted using pencil and paper, m ### Download Pre-Built Binaries -**[Download Current Linux x86](https://git.laantungir.net/laantungir/otp/releases/download/v0.3.38/otp-v0.3.38-linux-x86_64)** +**[Download Current Linux x86](https://git.laantungir.net/laantungir/otp/releases/download/v0.3.39/otp-v0.3.39-linux-x86_64)** -**[Download Current Raspberry Pi 64](https://git.laantungir.net/laantungir/otp/releases/download/v0.3.38/otp-v0.3.38-linux-arm64)** +**[Download Current Raspberry Pi 64](https://git.laantungir.net/laantungir/otp/releases/download/v0.3.39/otp-v0.3.39-linux-arm64)** After downloading: ```bash # Rename for convenience, then make executable -mv otp-v0.3.38-linux-x86_64 otp +mv otp-v0.3.39-linux-x86_64 otp chmod +x otp # Run it diff --git a/src/archive.c b/src/archive.c new file mode 100644 index 0000000..fdbb382 --- /dev/null +++ b/src/archive.c @@ -0,0 +1,488 @@ +#define _POSIX_C_SOURCE 200809L +#define _DEFAULT_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include "main.h" +#include "microtar/microtar.h" +#include "miniz/miniz.h" + +//////////////////////////////////////////////////////////////////////////////// +// DIRECTORY ARCHIVING FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// + +// Helper function to recursively add directory contents to TAR archive +static int add_directory_to_tar(mtar_t* tar, const char* base_path, const char* relative_path) { + DIR* dir = opendir(base_path); + if (!dir) { + printf("Error: Cannot open directory '%s'\n", base_path); + return 1; + } + + struct dirent* entry; + while ((entry = readdir(dir)) != NULL) { + // Skip . and .. + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + + // Build full path + char full_path[2048]; + snprintf(full_path, sizeof(full_path), "%s/%s", base_path, entry->d_name); + + // Build relative path for TAR + char tar_path[2048]; + if (strlen(relative_path) > 0) { + snprintf(tar_path, sizeof(tar_path), "%s/%s", relative_path, entry->d_name); + } else { + snprintf(tar_path, sizeof(tar_path), "%s", entry->d_name); + } + + struct stat st; + if (stat(full_path, &st) != 0) { + printf("Warning: Cannot stat '%s', skipping\n", full_path); + continue; + } + + if (S_ISDIR(st.st_mode)) { + // Recursively add subdirectory + if (add_directory_to_tar(tar, full_path, tar_path) != 0) { + closedir(dir); + return 1; + } + } else if (S_ISREG(st.st_mode)) { + // Add regular file + FILE* fp = fopen(full_path, "rb"); + if (!fp) { + printf("Warning: Cannot open '%s', skipping\n", full_path); + continue; + } + + // Get file size + fseek(fp, 0, SEEK_END); + size_t file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + + // Read file data + unsigned char* file_data = malloc(file_size); + if (!file_data) { + printf("Error: Memory allocation failed for '%s'\n", full_path); + fclose(fp); + closedir(dir); + return 1; + } + + size_t bytes_read = fread(file_data, 1, file_size, fp); + fclose(fp); + + if (bytes_read != file_size) { + printf("Warning: Could not read entire file '%s', skipping\n", full_path); + free(file_data); + continue; + } + + // Write to TAR + if (mtar_write_file_header(tar, tar_path, file_size) != MTAR_ESUCCESS) { + printf("Error: Failed to write TAR header for '%s'\n", tar_path); + free(file_data); + closedir(dir); + return 1; + } + + if (mtar_write_data(tar, file_data, file_size) != MTAR_ESUCCESS) { + printf("Error: Failed to write TAR data for '%s'\n", tar_path); + free(file_data); + closedir(dir); + return 1; + } + + free(file_data); + } + } + + closedir(dir); + return 0; +} + +// Create TAR archive from directory +int create_tar_archive(const char* dir_path, const char* tar_output_path) { + mtar_t tar; + + if (mtar_open(&tar, tar_output_path, "w") != MTAR_ESUCCESS) { + printf("Error: Cannot create TAR file '%s'\n", tar_output_path); + return 1; + } + + // Get directory name for relative paths + char dir_name[512]; + const char* last_slash = strrchr(dir_path, '/'); + if (last_slash) { + strncpy(dir_name, last_slash + 1, sizeof(dir_name) - 1); + } else { + strncpy(dir_name, dir_path, sizeof(dir_name) - 1); + } + dir_name[sizeof(dir_name) - 1] = '\0'; + + // Add directory contents to TAR + int result = add_directory_to_tar(&tar, dir_path, dir_name); + + // Finalize and close TAR + mtar_finalize(&tar); + mtar_close(&tar); + + return result; +} + +// Extract TAR archive to directory +int extract_tar_archive(const char* tar_path, const char* output_dir) { + mtar_t tar; + mtar_header_t header; + + if (mtar_open(&tar, tar_path, "r") != MTAR_ESUCCESS) { + printf("Error: Cannot open TAR file '%s'\n", tar_path); + return 1; + } + + // Create output directory if it doesn't exist + mkdir(output_dir, 0755); + + // Extract each file + while (mtar_read_header(&tar, &header) == MTAR_ESUCCESS) { + char output_path[2048]; + snprintf(output_path, sizeof(output_path), "%s/%s", output_dir, header.name); + + // Create parent directories + char* last_slash = strrchr(output_path, '/'); + if (last_slash) { + char parent_dir[2048]; + strncpy(parent_dir, output_path, last_slash - output_path); + parent_dir[last_slash - output_path] = '\0'; + + // Create directories recursively + char* p = parent_dir; + while (*p) { + if (*p == '/') { + *p = '\0'; + mkdir(parent_dir, 0755); + *p = '/'; + } + p++; + } + mkdir(parent_dir, 0755); + } + + // Extract file data + unsigned char* data = malloc(header.size); + if (!data) { + printf("Error: Memory allocation failed\n"); + mtar_close(&tar); + return 1; + } + + if (mtar_read_data(&tar, data, header.size) != MTAR_ESUCCESS) { + printf("Error: Failed to read data for '%s'\n", header.name); + free(data); + mtar_close(&tar); + return 1; + } + + // Write to file + FILE* fp = fopen(output_path, "wb"); + if (!fp) { + printf("Error: Cannot create file '%s'\n", output_path); + free(data); + mtar_close(&tar); + return 1; + } + + fwrite(data, 1, header.size, fp); + fclose(fp); + free(data); + + mtar_next(&tar); + } + + mtar_close(&tar); + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +// COMPRESSION FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// + +// Compress file with gzip (miniz) +int compress_file_gzip(const char* input_path, const char* output_path) { + // Read input file + FILE* in = fopen(input_path, "rb"); + if (!in) { + printf("Error: Cannot open input file '%s'\n", input_path); + return 1; + } + + fseek(in, 0, SEEK_END); + size_t input_size = ftell(in); + fseek(in, 0, SEEK_SET); + + unsigned char* input_data = malloc(input_size); + if (!input_data) { + printf("Error: Memory allocation failed\n"); + fclose(in); + return 1; + } + + size_t bytes_read = fread(input_data, 1, input_size, in); + fclose(in); + + if (bytes_read != input_size) { + printf("Error: Failed to read input file\n"); + free(input_data); + return 1; + } + + // Compress with miniz + mz_ulong compressed_size = compressBound(input_size); + unsigned char* compressed_data = malloc(compressed_size); + if (!compressed_data) { + printf("Error: Memory allocation failed\n"); + free(input_data); + return 1; + } + + int result = compress2(compressed_data, &compressed_size, + input_data, input_size, + MZ_BEST_COMPRESSION); + + free(input_data); + + if (result != MZ_OK) { + printf("Error: Compression failed (error code: %d)\n", result); + free(compressed_data); + return 1; + } + + // Write compressed data + FILE* out = fopen(output_path, "wb"); + if (!out) { + printf("Error: Cannot create output file '%s'\n", output_path); + free(compressed_data); + return 1; + } + + fwrite(compressed_data, 1, compressed_size, out); + fclose(out); + free(compressed_data); + + return 0; +} + +// Decompress gzip file (miniz) +int decompress_file_gzip(const char* input_path, const char* output_path) { + // Read compressed file + FILE* in = fopen(input_path, "rb"); + if (!in) { + printf("Error: Cannot open compressed file '%s'\n", input_path); + return 1; + } + + fseek(in, 0, SEEK_END); + size_t compressed_size = ftell(in); + fseek(in, 0, SEEK_SET); + + unsigned char* compressed_data = malloc(compressed_size); + if (!compressed_data) { + printf("Error: Memory allocation failed\n"); + fclose(in); + return 1; + } + + size_t bytes_read = fread(compressed_data, 1, compressed_size, in); + fclose(in); + + if (bytes_read != compressed_size) { + printf("Error: Failed to read compressed file\n"); + free(compressed_data); + return 1; + } + + // Estimate decompressed size (try multiple times if needed) + mz_ulong output_size = compressed_size * 10; + unsigned char* output_data = NULL; + int result; + + for (int attempt = 0; attempt < 3; attempt++) { + output_data = realloc(output_data, output_size); + if (!output_data) { + printf("Error: Memory allocation failed\n"); + free(compressed_data); + return 1; + } + + mz_ulong temp_size = output_size; + result = uncompress(output_data, &temp_size, compressed_data, compressed_size); + + if (result == MZ_OK) { + output_size = temp_size; + break; + } else if (result == MZ_BUF_ERROR) { + // Buffer too small, try larger + output_size *= 2; + } else { + printf("Error: Decompression failed (error code: %d)\n", result); + free(compressed_data); + free(output_data); + return 1; + } + } + + free(compressed_data); + + if (result != MZ_OK) { + printf("Error: Decompression failed after multiple attempts\n"); + free(output_data); + return 1; + } + + // Write decompressed data + FILE* out = fopen(output_path, "wb"); + if (!out) { + printf("Error: Cannot create output file '%s'\n", output_path); + free(output_data); + return 1; + } + + fwrite(output_data, 1, output_size, out); + fclose(out); + free(output_data); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +// HIGH-LEVEL DIRECTORY ENCRYPTION/DECRYPTION +//////////////////////////////////////////////////////////////////////////////// + +// Encrypt directory: TAR → GZIP → Encrypt +int encrypt_directory(const char* dir_path, const char* pad_identifier, const char* output_file) { + char temp_tar[512]; + char temp_gz[512]; + int result = 0; + + // Generate temporary file paths + snprintf(temp_tar, sizeof(temp_tar), "/tmp/otp_tar_%d.tar", getpid()); + snprintf(temp_gz, sizeof(temp_gz), "/tmp/otp_gz_%d.tar.gz", getpid()); + + printf("Creating TAR archive...\n"); + if (create_tar_archive(dir_path, temp_tar) != 0) { + printf("Error: Failed to create TAR archive\n"); + return 1; + } + + printf("Compressing archive...\n"); + if (compress_file_gzip(temp_tar, temp_gz) != 0) { + printf("Error: Failed to compress archive\n"); + unlink(temp_tar); + return 1; + } + + printf("Encrypting compressed archive...\n"); + result = encrypt_file(pad_identifier, temp_gz, output_file, 0); + + // Cleanup temporary files + unlink(temp_tar); + unlink(temp_gz); + + if (result == 0) { + printf("Directory encrypted successfully: %s\n", output_file); + } + + return result; +} + +// Detect if file is a compressed TAR archive +int is_compressed_tar_archive(const char* file_path) { + FILE* fp = fopen(file_path, "rb"); + if (!fp) { + return 0; + } + + unsigned char magic[512]; + size_t bytes_read = fread(magic, 1, sizeof(magic), fp); + fclose(fp); + + if (bytes_read < 2) { + return 0; + } + + // Check for GZIP magic bytes (0x1f 0x8b) + if (magic[0] == 0x1f && magic[1] == 0x8b) { + return 1; + } + + // Check for TAR magic ("ustar" at offset 257) + if (bytes_read >= 262 && memcmp(magic + 257, "ustar", 5) == 0) { + return 1; + } + + return 0; +} + +// Decrypt and extract directory: Decrypt → GUNZIP → Extract TAR +int decrypt_and_extract_directory(const char* encrypted_file, const char* output_dir) { + char temp_decrypted[512]; + char temp_tar[512]; + int result = 0; + + // Generate temporary file paths + snprintf(temp_decrypted, sizeof(temp_decrypted), "/tmp/otp_decrypt_%d", getpid()); + snprintf(temp_tar, sizeof(temp_tar), "/tmp/otp_tar_%d.tar", getpid()); + + printf("Decrypting file...\n"); + if (decrypt_file(encrypted_file, temp_decrypted) != 0) { + printf("Error: Failed to decrypt file\n"); + return 1; + } + + // Check if it's compressed + FILE* fp = fopen(temp_decrypted, "rb"); + if (!fp) { + printf("Error: Cannot open decrypted file\n"); + unlink(temp_decrypted); + return 1; + } + + unsigned char magic[2]; + fread(magic, 1, 2, fp); + fclose(fp); + + if (magic[0] == 0x1f && magic[1] == 0x8b) { + // GZIP compressed + printf("Decompressing archive...\n"); + if (decompress_file_gzip(temp_decrypted, temp_tar) != 0) { + printf("Error: Failed to decompress archive\n"); + unlink(temp_decrypted); + return 1; + } + unlink(temp_decrypted); + } else { + // Not compressed, assume it's already TAR + rename(temp_decrypted, temp_tar); + } + + printf("Extracting archive...\n"); + result = extract_tar_archive(temp_tar, output_dir); + + // Cleanup + unlink(temp_tar); + + if (result == 0) { + printf("Directory extracted successfully to: %s\n", output_dir); + } + + return result; +} diff --git a/src/main.h b/src/main.h index 9365dcf..0fcce78 100644 --- a/src/main.h +++ b/src/main.h @@ -23,7 +23,7 @@ #include // Version - Updated automatically by build.sh -#define OTP_VERSION "v0.3.38" +#define OTP_VERSION "v0.3.39" // Constants #define MAX_INPUT_SIZE 4096 @@ -130,6 +130,7 @@ char* get_preferred_editor(void); char* get_preferred_file_manager(void); int launch_text_editor(const char* initial_content, char* result_buffer, size_t buffer_size); int launch_file_manager(const char* start_directory, char* selected_file, size_t buffer_size); +int launch_directory_manager(const char* start_directory, char* selected_dir, size_t buffer_size); //////////////////////////////////////////////////////////////////////////////// // CORE CRYPTOGRAPHIC OPERATIONS @@ -238,6 +239,23 @@ int update_pad_checksum_after_entropy(const char* old_chksum, char* new_chksum); int rename_pad_files_safely(const char* old_chksum, const char* new_chksum); int is_pad_unused(const char* pad_chksum); +//////////////////////////////////////////////////////////////////////////////// +// DIRECTORY ARCHIVING AND COMPRESSION FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// + +// Directory encryption/decryption (TAR + GZIP + OTP) +int encrypt_directory(const char* dir_path, const char* pad_identifier, const char* output_file); +int decrypt_and_extract_directory(const char* encrypted_file, const char* output_dir); +int is_compressed_tar_archive(const char* file_path); + +// TAR archive operations +int create_tar_archive(const char* dir_path, const char* tar_output_path); +int extract_tar_archive(const char* tar_path, const char* output_dir); + +// Compression operations +int compress_file_gzip(const char* input_path, const char* output_path); +int decompress_file_gzip(const char* input_path, const char* output_path); + //////////////////////////////////////////////////////////////////////////////// // DIRECTORY MANAGEMENT FUNCTIONS //////////////////////////////////////////////////////////////////////////////// @@ -314,6 +332,7 @@ int handle_decrypt_menu(void); int handle_pads_menu(void); int handle_text_encrypt(void); int handle_file_encrypt(void); +int handle_directory_encrypt(void); int handle_verify_pad(const char* pad_chksum); int handle_delete_pad(const char* pad_chksum); diff --git a/src/pads.c b/src/pads.c index 4b50c09..1ef9023 100644 --- a/src/pads.c +++ b/src/pads.c @@ -253,10 +253,10 @@ int generate_pad(uint64_t size_bytes, int display_progress) { } // Initialize state file with offset 32 (first 32 bytes reserved for checksum encryption) - FILE* state_file = fopen(state_path, "wb"); + FILE* state_file = fopen(state_path, "w"); if (state_file) { uint64_t reserved_bytes = 32; - fwrite(&reserved_bytes, sizeof(uint64_t), 1, state_file); + fprintf(state_file, "offset=%lu\n", reserved_bytes); fclose(state_file); } else { printf("Error: Failed to create state file\n"); @@ -292,7 +292,7 @@ int read_state_offset(const char* pad_chksum, uint64_t* offset) { return 0; } - // Try to read as text format first (new format) + // Read text format only (required format: "offset=") char line[128]; if (fgets(line, sizeof(line), state_file)) { // Check if it's text format (starts with "offset=") @@ -302,21 +302,13 @@ int read_state_offset(const char* pad_chksum, uint64_t* offset) { return 0; } - // Not text format, try binary format (legacy) + // Not in proper text format - error fclose(state_file); - state_file = fopen(state_filename, "rb"); - if (!state_file) { - *offset = 0; - return 0; - } - - if (fread(offset, sizeof(uint64_t), 1, state_file) != 1) { - fclose(state_file); - *offset = 0; - return 0; - } - fclose(state_file); - return 0; + fprintf(stderr, "Error: State file '%s' is not in proper text format\n", state_filename); + fprintf(stderr, "Expected format: offset=\n"); + fprintf(stderr, "Please convert old binary state files to text format\n"); + *offset = 0; + return 1; } fclose(state_file); diff --git a/src/ui.c b/src/ui.c index 8e00b8e..28b89f4 100644 --- a/src/ui.c +++ b/src/ui.c @@ -99,6 +99,9 @@ int interactive_mode(void) { case 'F': handle_file_encrypt(); break; + case 'R': + handle_directory_encrypt(); + break; case 'D': handle_decrypt_menu(); break; @@ -125,11 +128,12 @@ void show_main_menu(void) { print_centered_header(header, 0); printf("\n"); - printf(" \033[4mT\033[0mext encrypt\n"); //TEXT ENCRYPT - printf(" \033[4mF\033[0mile encrypt\n"); //FILE ENCRYPT - printf(" \033[4mD\033[0mecrypt\n"); //DECRYPT - printf(" \033[4mP\033[0mads\n"); //PADS - printf(" E\033[4mx\033[0mit\n"); //EXIT + printf(" \033[4mT\033[0mext encrypt\n"); //TEXT ENCRYPT + printf(" \033[4mF\033[0mile encrypt\n"); //FILE ENCRYPT + printf(" Di\033[4mr\033[0mectory encrypt\n"); //DIRECTORY ENCRYPT + printf(" \033[4mD\033[0mecrypt\n"); //DECRYPT + printf(" \033[4mP\033[0mads\n"); //PADS + printf(" E\033[4mx\033[0mit\n"); //EXIT printf("\nSelect option: "); } @@ -352,7 +356,23 @@ int handle_decrypt_menu(void) { return 1; } - return decrypt_file(selected_file, output_file); + // Check if it's a directory archive + if (strstr(selected_file, ".tar.gz.otp") || strstr(selected_file, ".tar.otp")) { + // It's a directory archive - extract to directory + char extract_dir[512]; + strncpy(extract_dir, output_file, sizeof(extract_dir) - 1); + extract_dir[sizeof(extract_dir) - 1] = '\0'; + + // Remove .tar.gz.otp or .tar.otp extension to get directory name + char* ext = strstr(extract_dir, ".tar.gz.otp"); + if (!ext) ext = strstr(extract_dir, ".tar.otp"); + if (ext) *ext = '\0'; + + printf("Extracting directory archive to: %s/\n", extract_dir); + return decrypt_and_extract_directory(selected_file, extract_dir); + } else { + return decrypt_file(selected_file, output_file); + } } else if (strncmp(input_line, "-----BEGIN OTP MESSAGE-----", 27) == 0) { // Looks like ASCII armor - collect the full message @@ -404,7 +424,23 @@ int handle_decrypt_menu(void) { return 1; } - return decrypt_file(input_line, output_file); + // Check if it's a directory archive + if (strstr(input_line, ".tar.gz.otp") || strstr(input_line, ".tar.otp")) { + // It's a directory archive - extract to directory + char extract_dir[512]; + strncpy(extract_dir, output_file, sizeof(extract_dir) - 1); + extract_dir[sizeof(extract_dir) - 1] = '\0'; + + // Remove .tar.gz.otp or .tar.otp extension to get directory name + char* ext = strstr(extract_dir, ".tar.gz.otp"); + if (!ext) ext = strstr(extract_dir, ".tar.otp"); + if (ext) *ext = '\0'; + + printf("Extracting directory archive to: %s/\n", extract_dir); + return decrypt_and_extract_directory(input_line, extract_dir); + } else { + return decrypt_file(input_line, output_file); + } } else { printf("Input not recognized as ASCII armor or valid file path.\n"); return 1; @@ -501,5 +537,86 @@ int handle_file_encrypt(void) { int result = encrypt_file(selected_pad, input_file, output_filename, ascii_armor); free(selected_pad); + return result; +} + +int handle_directory_encrypt(void) { + printf("\n"); + print_centered_header("Directory Encrypt", 0); + + // Directory selection options + printf("\nDirectory selection options:\n"); + printf(" 1. Type directory path directly\n"); + printf(" 2. Use file manager (navigate to directory)\n"); + printf("Enter choice (1-2): "); + + char choice_input[10]; + char dir_path[512]; + + if (!fgets(choice_input, sizeof(choice_input), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + + if (atoi(choice_input) == 2) { + // Use directory manager + if (launch_directory_manager(".", dir_path, sizeof(dir_path)) != 0) { + printf("Falling back to manual directory path entry.\n"); + printf("Enter directory path to encrypt: "); + if (!fgets(dir_path, sizeof(dir_path), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + dir_path[strcspn(dir_path, "\n")] = 0; + } + } else { + // Direct directory path input + printf("Enter directory path to encrypt: "); + if (!fgets(dir_path, sizeof(dir_path), stdin)) { + printf("Error: Failed to read input\n"); + return 1; + } + dir_path[strcspn(dir_path, "\n")] = 0; + } + + // Check if directory exists + struct stat st; + if (stat(dir_path, &st) != 0 || !S_ISDIR(st.st_mode)) { + printf("Error: '%s' is not a valid directory\n", dir_path); + return 1; + } + + // Select pad + char* selected_pad = select_pad_interactive("Select Pad for Directory Encryption", + "Select pad (by prefix)", + PAD_FILTER_ALL, 1); + if (!selected_pad) { + printf("Directory encryption cancelled.\n"); + return 1; + } + + // Generate default output filename + char default_output[1024]; + const char* dir_name = strrchr(dir_path, '/'); + if (dir_name) { + dir_name++; // Skip the '/' + } else { + dir_name = dir_path; + } + + snprintf(default_output, sizeof(default_output), "%s.tar.gz.otp", dir_name); + + // Get output filename + char output_file[512]; + if (get_filename_with_default("Output filename:", default_output, output_file, sizeof(output_file)) != 0) { + printf("Error: Failed to read input\n"); + free(selected_pad); + return 1; + } + + // Encrypt directory + int result = encrypt_directory(dir_path, selected_pad, output_file); + free(selected_pad); + return result; } \ No newline at end of file diff --git a/src/util.c b/src/util.c index 8515869..cf444e2 100644 --- a/src/util.c +++ b/src/util.c @@ -240,6 +240,83 @@ int launch_file_manager(const char* start_directory, char* selected_file, size_t return 1; // Fall back to manual entry } +int launch_directory_manager(const char* start_directory, char* selected_dir, size_t buffer_size) { + char* fm = get_preferred_file_manager(); + if (!fm) { + printf("No file manager found. Please install ranger, fzf, nnn, or lf.\n"); + printf("Falling back to manual directory path entry.\n"); + return 1; // Fall back to manual entry + } + + char temp_filename[64]; + snprintf(temp_filename, sizeof(temp_filename), "/tmp/otp_dir_%ld.tmp", time(NULL)); + + char command[512]; + int result = 1; + + printf("Opening %s for directory selection...\n", fm); + printf("Navigate INTO the directory you want to encrypt, then press 'q' to quit and select it.\n"); + + if (strcmp(fm, "ranger") == 0) { + snprintf(command, sizeof(command), "cd '%s' && ranger --choosedir=%s", + start_directory ? start_directory : ".", temp_filename); + } else if (strcmp(fm, "fzf") == 0) { + // fzf doesn't have directory-only mode easily, use find + snprintf(command, sizeof(command), "cd '%s' && find . -type d | fzf > %s", + start_directory ? start_directory : ".", temp_filename); + } else if (strcmp(fm, "nnn") == 0) { + snprintf(command, sizeof(command), "cd '%s' && nnn -p %s", + start_directory ? start_directory : ".", temp_filename); + } else if (strcmp(fm, "lf") == 0) { + snprintf(command, sizeof(command), "cd '%s' && lf -selection-path=%s", + start_directory ? start_directory : ".", temp_filename); + } + + result = system(command); + + if (result == 0 || result == 256) { // Some file managers return 256 on success + // Read selected directory from temp file + FILE* temp_file = fopen(temp_filename, "r"); + if (temp_file) { + if (fgets(selected_dir, buffer_size, temp_file)) { + // Remove trailing newline + selected_dir[strcspn(selected_dir, "\n\r")] = 0; + + // For relative paths, make absolute if needed + if (selected_dir[0] == '.' && selected_dir[1] == '/') { + char current_dir[512]; + if (getcwd(current_dir, sizeof(current_dir))) { + char abs_path[1024]; + snprintf(abs_path, sizeof(abs_path), "%s/%s", current_dir, selected_dir + 2); + strncpy(selected_dir, abs_path, buffer_size - 1); + selected_dir[buffer_size - 1] = '\0'; + } + } else if (selected_dir[0] != '/') { + // Relative path without ./ + char current_dir[512]; + if (getcwd(current_dir, sizeof(current_dir))) { + char abs_path[1024]; + snprintf(abs_path, sizeof(abs_path), "%s/%s", current_dir, selected_dir); + strncpy(selected_dir, abs_path, buffer_size - 1); + selected_dir[buffer_size - 1] = '\0'; + } + } + + fclose(temp_file); + unlink(temp_filename); + free(fm); + return 0; // Success + } + fclose(temp_file); + } + } + + // Clean up and indicate failure + unlink(temp_filename); + free(fm); + return 1; // Fall back to manual entry +} + // Stdin detection functions implementation int has_stdin_data(void) { // Check if stdin is a pipe/redirect (not a terminal)