Files
otp/otp.c
2025-09-21 15:55:06 -04:00

7254 lines
256 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#define _POSIX_C_SOURCE 200809L
#define _DEFAULT_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <dirent.h>
#include <time.h>
#include <ctype.h>
#include <termios.h>
#include <fcntl.h>
#include <math.h>
#include "nostr_chacha20.h"
#include "otp.h"
// Custom base64 character set
static const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const int base64_decode_table[256] = {
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63,
52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,
15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,
-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
};
#define MAX_INPUT_SIZE 4096
#define MAX_LINE_LENGTH 1024
#define MAX_HASH_LENGTH 65
#define PROGRESS_UPDATE_INTERVAL (64 * 1024 * 1024) // 64MB intervals
#define DEFAULT_PADS_DIR "pads"
#define FILES_DIR "files"
#define MAX_ENTROPY_BUFFER 32768 // 32KB entropy buffer
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// GLOBAL VARIABLES
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
static char current_pads_dir[512] = DEFAULT_PADS_DIR;
static char default_pad_path[1024] = "";
static int is_interactive_mode = 0;
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// MAIN
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// NEW SIMPLIFIED USB DETECTION SYSTEM v2
// Uses lsusb cross-referencing for reliable USB device identification
////////////////////////////////////////////////////////////////////////////////
/**
* Decode octal escape sequences in mount paths (e.g., \040 for space)
* Used by the new v2 USB detection system
*/
void decode_octal_escapes(const char* input, char* output, size_t output_size) {
if (!input || !output || output_size == 0) return;
size_t src = 0, dst = 0;
size_t input_len = strlen(input);
while (src < input_len && dst < output_size - 1) {
if (input[src] == '\\' && src + 3 < input_len &&
input[src + 1] >= '0' && input[src + 1] <= '7' &&
input[src + 2] >= '0' && input[src + 2] <= '7' &&
input[src + 3] >= '0' && input[src + 3] <= '7') {
// Decode octal escape sequence
int octal_val = (input[src + 1] - '0') * 64 +
(input[src + 2] - '0') * 8 +
(input[src + 3] - '0');
output[dst++] = (char)octal_val;
src += 4;
} else {
output[dst++] = input[src++];
}
}
output[dst] = '\0';
}
/**
* Build USB device registry by parsing lsusb output
* Returns array of USB devices found on system
*/
int build_usb_device_registry(usb_device_registry_t **devices, int *device_count) {
if (!devices || !device_count) return 0;
*devices = NULL;
*device_count = 0;
// Execute lsusb command and capture output
FILE *lsusb_pipe = popen("lsusb", "r");
if (!lsusb_pipe) {
fprintf(stderr, "Error: Failed to execute lsusb command\n");
return 0;
}
// Allocate initial array for USB devices
int capacity = 16;
usb_device_registry_t *registry = malloc(capacity * sizeof(usb_device_registry_t));
if (!registry) {
pclose(lsusb_pipe);
return 0;
}
char line[512];
int count = 0;
// Parse each line of lsusb output
// Format: "Bus 001 Device 003: ID 0781:5583 SanDisk Corp. Ultra Fit"
while (fgets(line, sizeof(line), lsusb_pipe)) {
// Expand array if needed
if (count >= capacity) {
capacity *= 2;
usb_device_registry_t *new_registry = realloc(registry, capacity * sizeof(usb_device_registry_t));
if (!new_registry) {
free(registry);
pclose(lsusb_pipe);
return 0;
}
registry = new_registry;
}
usb_device_registry_t *device = &registry[count];
memset(device, 0, sizeof(usb_device_registry_t));
// Parse: "Bus 001 Device 003: ID 0781:5583 SanDisk Corp. Ultra Fit"
char *pos = line;
// Extract bus number
if (sscanf(pos, "Bus %3s", device->bus_number) != 1) continue;
pos = strstr(pos, "Device ");
if (!pos) continue;
// Extract device number
pos += 7; // Skip "Device "
if (sscanf(pos, "%3s", device->device_number) != 1) continue;
// Find ID field
pos = strstr(pos, "ID ");
if (!pos) continue;
pos += 3; // Skip "ID "
// Extract vendor:product IDs
if (sscanf(pos, "%7[^:]:%7s", device->vendor_id, device->product_id) != 2) continue;
// Find start of vendor/product names after ID
pos = strchr(pos, ' ');
if (!pos) continue;
pos++; // Skip space
// Extract vendor and product names (rest of line)
char *newline = strchr(pos, '\n');
if (newline) *newline = '\0';
// Split vendor and product names
// Look for common patterns like "Corp.", "Inc.", "Ltd."
char *vendor_end = strstr(pos, " Corp.");
if (!vendor_end) vendor_end = strstr(pos, " Inc.");
if (!vendor_end) vendor_end = strstr(pos, " Ltd.");
if (!vendor_end) vendor_end = strstr(pos, " Co.");
if (vendor_end) {
// Include the company suffix
if (strstr(vendor_end, " Corp.")) vendor_end += 6;
else if (strstr(vendor_end, " Inc.")) vendor_end += 5;
else if (strstr(vendor_end, " Ltd.")) vendor_end += 5;
else if (strstr(vendor_end, " Co.")) vendor_end += 4;
size_t vendor_len = vendor_end - pos;
if (vendor_len >= sizeof(device->vendor_name)) vendor_len = sizeof(device->vendor_name) - 1;
strncpy(device->vendor_name, pos, vendor_len);
device->vendor_name[vendor_len] = '\0';
// Product name is what remains after vendor
char *product_start = vendor_end;
while (*product_start == ' ') product_start++; // Skip spaces
strncpy(device->product_name, product_start, sizeof(device->product_name) - 1);
} else {
// Fallback: split at first word boundary after reasonable vendor length
char *space = strchr(pos + 8, ' '); // Look for space after ~8 chars
if (space) {
size_t vendor_len = space - pos;
if (vendor_len >= sizeof(device->vendor_name)) vendor_len = sizeof(device->vendor_name) - 1;
strncpy(device->vendor_name, pos, vendor_len);
device->vendor_name[vendor_len] = '\0';
strncpy(device->product_name, space + 1, sizeof(device->product_name) - 1);
} else {
// Single word - treat as vendor
strncpy(device->vendor_name, pos, sizeof(device->vendor_name) - 1);
strcpy(device->product_name, "Unknown");
}
}
count++;
}
pclose(lsusb_pipe);
*devices = registry;
*device_count = count;
return 1;
}
/**
* Free USB device registry memory
*/
void free_usb_device_registry(usb_device_registry_t *devices) {
if (devices) {
free(devices);
}
}
/**
* Find USB device in registry by matching device path to USB bus info
* Maps /dev/sdX to USB device via /sys/block/sdX/device tree
*/
usb_device_registry_t* find_usb_device_by_path(const char* device_path,
usb_device_registry_t* usb_devices, int usb_count) {
if (!device_path || !usb_devices || usb_count <= 0) return NULL;
// Extract device name from path: /dev/sda1 -> sda
char device_name[16];
const char *dev_start = strrchr(device_path, '/');
if (!dev_start) dev_start = device_path;
else dev_start++; // Skip '/'
// Copy device name, removing partition number
strncpy(device_name, dev_start, sizeof(device_name) - 1);
device_name[sizeof(device_name) - 1] = '\0';
// Remove partition number (digits at end)
char *digit_pos = device_name;
while (*digit_pos && !isdigit(*digit_pos)) digit_pos++;
if (*digit_pos) *digit_pos = '\0'; // Truncate at first digit
// Read vendor and product info from sysfs
char vendor_path[256], product_path[256];
char model_path[256], manufacturer_path[256];
snprintf(vendor_path, sizeof(vendor_path), "/sys/block/%s/device/vendor", device_name);
snprintf(product_path, sizeof(product_path), "/sys/block/%s/device/model", device_name);
snprintf(manufacturer_path, sizeof(manufacturer_path), "/sys/block/%s/device/manufacturer", device_name);
snprintf(model_path, sizeof(model_path), "/sys/block/%s/device/product", device_name);
// Try to read device info from multiple possible sysfs locations
char vendor_info[64] = {0}, product_info[64] = {0};
FILE *fp;
// Try vendor file
fp = fopen(vendor_path, "r");
if (fp) {
fgets(vendor_info, sizeof(vendor_info), fp);
fclose(fp);
// Trim whitespace
char *end = vendor_info + strlen(vendor_info) - 1;
while (end > vendor_info && isspace(*end)) *end-- = '\0';
}
// Try product/model file
fp = fopen(product_path, "r");
if (!fp) fp = fopen(model_path, "r");
if (fp) {
fgets(product_info, sizeof(product_info), fp);
fclose(fp);
// Trim whitespace
char *end = product_info + strlen(product_info) - 1;
while (end > product_info && isspace(*end)) *end-- = '\0';
}
// If we couldn't get device info from sysfs, check if it's in common USB mount locations
if (strlen(vendor_info) == 0 && strlen(product_info) == 0) {
// For devices in /media/ or /mnt/, assume they could be USB and do partial matching
// This handles cases where sysfs info isn't available
return usb_count > 0 ? &usb_devices[0] : NULL; // Return first USB device as fallback
}
// Try to match vendor/product info with USB device registry
for (int i = 0; i < usb_count; i++) {
usb_device_registry_t *usb_dev = &usb_devices[i];
// Try partial string matching for vendor names
if (strlen(vendor_info) > 0 && strlen(usb_dev->vendor_name) > 0) {
if (strstr(usb_dev->vendor_name, vendor_info) || strstr(vendor_info, usb_dev->vendor_name)) {
return usb_dev;
}
}
// Try partial string matching for product names
if (strlen(product_info) > 0 && strlen(usb_dev->product_name) > 0) {
if (strstr(usb_dev->product_name, product_info) || strstr(product_info, usb_dev->product_name)) {
return usb_dev;
}
}
}
return NULL; // No USB device found for this path
}
/**
* Get user-friendly status string for USB safety status
*/
const char* get_usb_status_string(usb_safety_status_t status) {
switch (status) {
case USB_SAFE: return "SAFE";
case USB_TOO_LARGE: return "TOO_LARGE";
case USB_NOT_USB: return "NOT_USB";
default: return "UNKNOWN";
}
}
/**
* Detect drive information using new simplified v2 system
* Cross-references with USB device registry for validation
*/
int detect_drive_info_v2(const char* mount_path, drive_info_v2_t* drive_info,
usb_device_registry_t* usb_devices, int usb_count) {
if (!mount_path || !drive_info) return 0;
// Initialize drive info structure
memset(drive_info, 0, sizeof(drive_info_v2_t));
strncpy(drive_info->mount_path, mount_path, sizeof(drive_info->mount_path) - 1);
// Get device path using existing working Method 1 from get_usb_drive_info
FILE* mounts_file = fopen("/proc/mounts", "r");
if (!mounts_file) return 0;
char line[1024];
int device_found = 0;
char device_path[256] = {0};
while (fgets(line, sizeof(line), mounts_file)) {
char source_field[256], mount_field[256], fs_type[64];
if (sscanf(line, "%255s %255s %63s", source_field, mount_field, fs_type) == 3) {
// Decode octal escape sequences in mount path
char decoded_mount[512];
decode_octal_escapes(mount_field, decoded_mount, sizeof(decoded_mount));
if (strcmp(decoded_mount, mount_path) == 0) {
strncpy(device_path, source_field, sizeof(device_path) - 1);
strncpy(drive_info->filesystem, fs_type, sizeof(drive_info->filesystem) - 1);
device_found = 1;
break;
}
}
}
fclose(mounts_file);
if (!device_found) return 0;
// CRITICAL FIX: Filter out internal encrypted drives (luks/mapper devices)
if (strstr(device_path, "/dev/mapper/") || strstr(device_path, "luks-")) {
return 0; // Skip internal encrypted drives - these are not USB devices
}
// Store device path and extract device name
strncpy(drive_info->device_path, device_path, sizeof(drive_info->device_path) - 1);
const char *dev_start = strrchr(device_path, '/');
if (dev_start) dev_start++; else dev_start = device_path;
char temp_name[16];
strncpy(temp_name, dev_start, sizeof(temp_name) - 1);
temp_name[sizeof(temp_name) - 1] = '\0';
// Remove partition number to get base device name
char *digit_pos = temp_name;
while (*digit_pos && !isdigit(*digit_pos)) digit_pos++;
if (*digit_pos) *digit_pos = '\0';
strncpy(drive_info->device_name, temp_name, sizeof(drive_info->device_name) - 1);
// ENHANCED FIX: Validate USB connectivity using sysfs path check
char sysfs_path[512];
char link_target[512];
snprintf(sysfs_path, sizeof(sysfs_path), "/sys/block/%s", drive_info->device_name);
ssize_t link_len = readlink(sysfs_path, link_target, sizeof(link_target) - 1);
if (link_len > 0) {
link_target[link_len] = '\0';
// Check if the device path contains 'usb' - genuine USB devices have USB paths
if (!strstr(link_target, "/usb")) {
return 0; // Skip non-USB devices based on sysfs path validation
}
} else {
return 0; // Skip devices without valid sysfs paths
}
// Extract label from mount path
const char *label_start = strrchr(mount_path, '/');
if (label_start) {
label_start++; // Skip '/'
strncpy(drive_info->label, label_start, sizeof(drive_info->label) - 1);
}
// Get filesystem stats for sizes
struct statvfs vfs_stat;
if (statvfs(mount_path, &vfs_stat) == 0) {
drive_info->total_size = (uint64_t)vfs_stat.f_blocks * vfs_stat.f_frsize;
drive_info->free_size = (uint64_t)vfs_stat.f_bavail * vfs_stat.f_frsize;
}
// Count .pad files on drive
DIR *dir = opendir(mount_path);
if (dir) {
struct dirent *entry;
while ((entry = readdir(dir))) {
if (strstr(entry->d_name, ".pad")) {
drive_info->pad_count++;
}
}
closedir(dir);
}
// Cross-reference with USB device registry
drive_info->usb_device = find_usb_device_by_path(device_path, usb_devices, usb_count);
// Determine safety status
if (drive_info->usb_device) {
// Found in USB registry - check size limits
if (drive_info->total_size > (6ULL * 1024 * 1024 * 1024 * 1024)) { // 6TB limit
drive_info->status = USB_TOO_LARGE;
} else {
drive_info->status = USB_SAFE;
}
} else {
drive_info->status = USB_NOT_USB;
}
return 1;
}
/**
* New simplified USB drive listing function
* Replaces the complex list_usb_drives with lsusb cross-referencing
*/
void list_usb_drives_v2(void) {
// Build USB device registry from lsusb
usb_device_registry_t *usb_devices = NULL;
int usb_count = 0;
if (!build_usb_device_registry(&usb_devices, &usb_count)) {
printf("Error: Failed to build USB device registry\n");
return;
}
// Parse mounted drives from /proc/mounts
FILE* mounts_file = fopen("/proc/mounts", "r");
if (!mounts_file) {
printf("Error: Cannot read /proc/mounts\n");
free_usb_device_registry(usb_devices);
return;
}
drive_info_v2_t drives[MAX_USB_DRIVES];
int drive_count = 0;
char line[1024];
while (fgets(line, sizeof(line), mounts_file) && drive_count < MAX_USB_DRIVES) {
char mount_point[512];
if (sscanf(line, "%*s %511s", mount_point) == 1) {
// Decode octal escapes
char decoded_mount[512];
decode_octal_escapes(mount_point, decoded_mount, sizeof(decoded_mount));
// Only check drives in common user mount locations
if (strstr(decoded_mount, "/media/") || strstr(decoded_mount, "/mnt/")) {
drive_info_v2_t drive_info;
if (detect_drive_info_v2(decoded_mount, &drive_info, usb_devices, usb_count)) {
drives[drive_count] = drive_info;
drive_count++;
}
}
}
}
fclose(mounts_file);
// Display results
if (drive_count == 0) {
printf("No drives found in /media/ or /mnt/ locations.\n");
} else {
printf("%-20s %-10s %-12s %-12s %-8s %-4s %-30s\n",
"Label", "Type", "Total", "Free", "FS", "Pads", "Mount Path");
printf("%-20s %-10s %-12s %-12s %-8s %-4s %-30s\n",
"--------------------", "----------", "------------", "------------",
"--------", "----", "------------------------------");
for (int i = 0; i < drive_count; i++) {
drive_info_v2_t *drive = &drives[i];
char total_str[32], free_str[32];
format_size_string(drive->total_size, total_str, sizeof(total_str));
format_size_string(drive->free_size, free_str, sizeof(free_str));
const char *type = drive->usb_device ? "USB" : "Local";
printf("%-20s %-10s %-12s %-12s %-8s %-4d %-30s\n",
drive->label, type, total_str, free_str, drive->filesystem,
drive->pad_count, drive->mount_path);
}
}
free_usb_device_registry(usb_devices);
}
int main(int argc, char* argv[]) {
// Load preferences first
load_preferences();
// Detect interactive mode: only true when running with no arguments
is_interactive_mode = (argc == 1);
// Check for OTP thumb drive on startup
char otp_drive_path[512];
if (detect_otp_thumb_drive(otp_drive_path, sizeof(otp_drive_path))) {
// Only show messages in interactive mode
if (is_interactive_mode) {
printf("Detected OTP thumb drive: %s\n", otp_drive_path);
printf("Using as default pads directory for this session.\n\n");
}
strncpy(current_pads_dir, otp_drive_path, sizeof(current_pads_dir) - 1);
current_pads_dir[sizeof(current_pads_dir) - 1] = '\0';
}
if (is_interactive_mode) {
return interactive_mode();
} else {
return command_line_mode(argc, argv);
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// COMMAND LINE MODE
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
int command_line_mode(int argc, char* argv[]) {
// Check for help flags first
if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--h") == 0 ||
strcmp(argv[1], "-help") == 0 || strcmp(argv[1], "--help") == 0 ||
strcmp(argv[1], "help") == 0) {
print_usage(argv[0]);
return 0;
}
if (strcmp(argv[1], "generate") == 0 || strcmp(argv[1], "-g") == 0) {
if (argc != 3) {
printf("Usage: %s generate|-g <size>\n", argv[0]);
printf("Size examples: 1024, 1GB, 5TB, 512MB\n");
return 1;
}
uint64_t size = parse_size_string(argv[2]);
if (size == 0) {
printf("Error: Invalid size format\n");
return 1;
}
return generate_pad(size, 1); // Use simplified pad generation
}
else if (strcmp(argv[1], "encrypt") == 0 || strcmp(argv[1], "-e") == 0) {
// Check for piped input first
if (has_stdin_data()) {
char* piped_text = read_stdin_text();
if (piped_text) {
int result = pipe_mode(argc, argv, piped_text);
free(piped_text);
return result;
}
}
if (argc < 2 || argc > 4) {
printf("Usage: %s encrypt|-e [pad_chksum_or_prefix] [text_to_encrypt]\n", argv[0]);
return 1;
}
// Check if pad was specified or use default
const char* pad_identifier = NULL;
const char* text = NULL;
if (argc == 2) {
// Just -e, use default pad, no text (interactive)
pad_identifier = NULL;
text = NULL;
} else if (argc == 3) {
// Could be -e <pad> or -e <text> (using default pad)
// Check if default pad is available to determine interpretation
char* default_pad = get_default_pad_path();
if (default_pad) {
// Default pad available, treat argument as text
pad_identifier = NULL;
text = argv[2];
free(default_pad);
} else {
// No default pad, treat as pad identifier
pad_identifier = argv[2];
text = NULL;
}
} else {
// argc == 4: -e <pad> <text>
pad_identifier = argv[2];
text = argv[3];
}
// If pad_identifier is NULL, we need to use default pad
if (pad_identifier == NULL) {
char* default_pad = get_default_pad_path();
if (default_pad) {
// Extract checksum from default pad path
char* filename = strrchr(default_pad, '/');
if (!filename) filename = default_pad;
else filename++; // Skip the '/'
// Extract checksum (remove .pad extension)
if (strlen(filename) >= 68 && strstr(filename, ".pad")) {
static char default_checksum[65];
strncpy(default_checksum, filename, 64);
default_checksum[64] = '\0';
pad_identifier = default_checksum;
}
free(default_pad);
// Call encrypt_text and return result
return encrypt_text(pad_identifier, text);
} else {
printf("Error: No default pad configured. Specify pad explicitly or configure default pad.\n");
return 1;
}
} else {
// Explicit pad specified, normal operation
return encrypt_text(pad_identifier, text);
}
}
else if (strcmp(argv[1], "decrypt") == 0 || strcmp(argv[1], "-d") == 0) {
if (argc == 2) {
// Check for piped input first
if (has_stdin_data()) {
// Piped decrypt mode - read stdin and decrypt silently
char* piped_message = read_stdin_text();
if (piped_message) {
int result = decrypt_text(NULL, piped_message);
free(piped_message);
return result;
}
}
// Interactive mode - no arguments needed
return decrypt_text(NULL, NULL);
}
else if (argc == 3) {
// Check if the argument looks like an encrypted message (starts with -----)
if (strncmp(argv[2], "-----BEGIN OTP MESSAGE-----", 27) == 0) {
// Inline decrypt with message only - use silent mode for command line
return decrypt_text(NULL, argv[2]);
} else {
// Check if it's a file (contains . or ends with known extensions)
if (strstr(argv[2], ".") != NULL) {
// Treat as file
return decrypt_file(argv[2], NULL);
} else {
// Interactive decrypt with pad hint (legacy support)
return decrypt_text(argv[2], NULL);
}
}
}
else if (argc == 4) {
// Check for -o flag for output file
if (strcmp(argv[2], "-o") == 0) {
printf("Usage: %s decrypt|-d <input_file> [-o <output_file>]\n", argv[0]);
return 1;
} else {
// Legacy format: pad_chksum and message, or file with output
// Use silent mode for command line when message is provided
return decrypt_text(argv[2], argv[3]);
}
}
else if (argc == 5 && strcmp(argv[3], "-o") == 0) {
// File decryption with output: -d <input_file> -o <output_file>
return decrypt_file(argv[2], argv[4]);
}
else {
printf("Usage: %s decrypt|-d [encrypted_message|file] [-o output_file]\n", argv[0]);
printf(" %s decrypt|-d [encrypted_message] (pad info from message)\n", argv[0]);
return 1;
}
}
else if (strcmp(argv[1], "-f") == 0) {
// File encryption mode: -f <input_file> <pad_prefix> [-a] [-o <output_file>]
if (argc < 4) {
printf("Usage: %s -f <input_file> <pad_prefix> [-a] [-o <output_file>]\n", argv[0]);
return 1;
}
const char* input_file = argv[2];
const char* pad_prefix = argv[3];
int ascii_armor = 0;
const char* output_file = NULL;
// Parse optional flags
for (int i = 4; i < argc; i++) {
if (strcmp(argv[i], "-a") == 0) {
ascii_armor = 1;
} else if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) {
output_file = argv[++i];
}
}
return encrypt_file(pad_prefix, input_file, output_file, ascii_armor);
}
else if (strcmp(argv[1], "list") == 0 || strcmp(argv[1], "-l") == 0) {
printf("Available pads:\n");
char* selected = select_pad_interactive("Available pads:", "Select pad (or press Enter to exit)", PAD_FILTER_ALL, 0);
if (selected) {
free(selected);
}
return 0;
}
else if (strcmp(argv[1], "usb-list") == 0) {
list_usb_drives_v2();
return 0;
}
else if (strcmp(argv[1], "usb-list-v2") == 0) {
list_usb_drives_v2();
return 0;
}
else if (strcmp(argv[1], "usb-init") == 0) {
if (argc < 3) {
printf("Usage: %s usb-init <drive_name> [drive_path]\n", argv[0]);
printf("Examples:\n");
printf(" %s usb-init OTP_BACKUP (interactive drive selection)\n", argv[0]);
printf(" %s usb-init OTP_BACKUP /media/user/USB_DRIVE\n", argv[0]);
return 1;
}
return handle_usb_init_cli(argc, argv);
}
else if (strcmp(argv[1], "usb-copy") == 0) {
if (argc < 4) {
printf("Usage: %s usb-copy <pad_prefix> <usb_drive_path>\n", argv[0]);
printf("Example: %s usb-copy 1a2b3c /media/user/OTP_BACKUP\n", argv[0]);
return 1;
}
return handle_usb_copy_cli(argv[2], argv[3]);
}
else if (strcmp(argv[1], "usb-import") == 0) {
if (argc < 4) {
printf("Usage: %s usb-import <pad_checksum> <usb_drive_path>\n", argv[0]);
printf("Example: %s usb-import 1a2b3c4d... /media/user/OTP_BACKUP\n", argv[0]);
return 1;
}
return handle_usb_import_cli(argv[2], argv[3]);
}
else if (strcmp(argv[1], "usb-verify") == 0) {
if (argc < 3) {
printf("Usage: %s usb-verify <usb_drive_path>\n", argv[0]);
printf("Example: %s usb-verify /media/user/OTP_BACKUP\n", argv[0]);
return 1;
}
return handle_usb_verify_cli(argv[2]);
}
else {
print_usage(argv[0]);
return 1;
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// INTERACTIVE MODE
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
int interactive_mode(void) {
char input[10];
printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
while (1) {
show_main_menu();
if (!fgets(input, sizeof(input), stdin)) {
printf("Goodbye!\n");
break;
}
char choice = toupper(input[0]);
switch (choice) {
case 'T':
handle_text_encrypt();
break;
case 'F':
handle_file_encrypt();
break;
case 'D':
handle_decrypt_menu();
break;
case 'P':
handle_pads_menu();
break;
case 'X':
case 'Q':
printf("Goodbye!\n");
return 0;
default:
printf("Invalid choice. Please try again.\n");
break;
}
}
return 0;
}
void show_main_menu(void) {
printf("\n=========================== Main Menu - OTP v0.3.7 ===========================\n\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("\nSelect option: ");
}
int handle_generate_menu(void) {
printf("\n=== Generate New Pad ===\n");
printf("Enter pad size (examples: 1GB, 5TB, 512MB, 2048): ");
char size_input[64];
if (!fgets(size_input, sizeof(size_input), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
size_input[strcspn(size_input, "\n")] = 0;
uint64_t size = parse_size_string(size_input);
if (size == 0) {
printf("Error: Invalid size format\n");
return 1;
}
double size_gb = (double)size / (1024.0 * 1024.0 * 1024.0);
printf("Generating %.2f GB pad...\n", size_gb);
printf("Note: Use 'Add entropy' in Pads menu to enhance randomness after creation.\n");
return generate_pad(size, 1);
}
int handle_encrypt_menu(void) {
printf("\n=== Encrypt Data ===\n");
printf("Available pads:\n");
char* selected = select_pad_interactive("Available pads:", "Select pad (or press Enter to continue)", PAD_FILTER_ALL, 0);
int pad_count = 1; // Assume at least 1 pad if function returned
if (selected) {
free(selected);
} else {
pad_count = 0;
}
if (pad_count == 0) {
printf("No pads available. Generate a pad first.\n");
return 1;
}
// Ask user to choose between text and file encryption
printf("\nSelect encryption type:\n");
printf(" 1. Text message\n");
printf(" 2. File\n");
printf("Enter choice (1-2): ");
char choice_input[10];
if (!fgets(choice_input, sizeof(choice_input), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
int choice = atoi(choice_input);
if (choice == 1) {
// Text encryption - use unified pad selection
char* selected_pad = select_pad_interactive("=== Select Pad for Text Encryption ===",
"Select pad (by prefix)",
PAD_FILTER_ALL, 1);
if (!selected_pad) {
printf("Text encryption cancelled.\n");
return 1;
}
int result = encrypt_text(selected_pad, NULL); // NULL for interactive mode
free(selected_pad);
return result;
}
else if (choice == 2) {
// File encryption
printf("\nFile selection options:\n");
printf(" 1. Type file path directly\n");
printf(" 2. Use file manager\n");
printf("Enter choice (1-2): ");
char file_choice[10];
char input_file[512];
if (!fgets(file_choice, sizeof(file_choice), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
if (atoi(file_choice) == 2) {
// Use file manager
if (launch_file_manager(".", input_file, sizeof(input_file)) != 0) {
printf("Falling back to manual file path entry.\n");
printf("Enter input file path: ");
if (!fgets(input_file, sizeof(input_file), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
input_file[strcspn(input_file, "\n")] = 0;
}
} else {
// Direct file path input
printf("Enter input file path: ");
if (!fgets(input_file, sizeof(input_file), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
input_file[strcspn(input_file, "\n")] = 0;
}
// Check if file exists
if (access(input_file, R_OK) != 0) {
printf("Error: File '%s' not found or cannot be read\n", input_file);
return 1;
}
// Use unified pad selection
char* selected_pad = select_pad_interactive("=== Select Pad for File Encryption ===",
"Select pad (by prefix)",
PAD_FILTER_ALL, 1);
if (!selected_pad) {
printf("File encryption cancelled.\n");
return 1;
}
// Ask for output format
printf("\nSelect output format:\n");
printf(" 1. Binary (.otp) - preserves file permissions\n");
printf(" 2. ASCII (.otp.asc) - text-safe format\n");
printf("Enter choice (1-2): ");
char format_input[10];
if (!fgets(format_input, sizeof(format_input), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
int ascii_armor = (atoi(format_input) == 2) ? 1 : 0;
// Generate default output filename with files directory and use enhanced input function
char default_output[1024]; // Increased size to prevent truncation warnings
char temp_default[1024];
// Generate base filename with appropriate extension
if (ascii_armor) {
snprintf(temp_default, sizeof(temp_default), "%s.otp.asc", input_file);
} else {
snprintf(temp_default, sizeof(temp_default), "%s.otp", input_file);
}
// Apply files directory default path
get_default_file_path(temp_default, default_output, sizeof(default_output));
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");
return 1;
}
const char* output_filename = output_file;
int result = encrypt_file(selected_pad, input_file, output_filename, ascii_armor);
free(selected_pad);
return result;
}
else {
printf("Invalid choice. Please enter 1 or 2.\n");
return 1;
}
}
int handle_decrypt_menu(void) {
printf("\n=== Smart Decrypt ===\n");
printf("Enter encrypted data (paste ASCII armor), file path, or press Enter to browse files:\n");
char input_line[MAX_LINE_LENGTH];
if (!fgets(input_line, sizeof(input_line), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
// Remove newline
input_line[strcspn(input_line, "\n")] = 0;
if (strlen(input_line) == 0) {
// Empty input - launch file manager to browse for files
char selected_file[512];
if (launch_file_manager(get_files_directory(), selected_file, sizeof(selected_file)) != 0) {
printf("Error: Could not launch file manager\n");
return 1;
}
// Generate smart default output filename with files directory and use enhanced input function
char temp_default[512];
char default_output[512];
strncpy(temp_default, selected_file, sizeof(temp_default) - 1);
temp_default[sizeof(temp_default) - 1] = '\0';
// Remove common encrypted extensions to get a better default
if (strstr(temp_default, ".otp.asc")) {
// Replace .otp.asc with original extension or no extension
char* ext_pos = strstr(temp_default, ".otp.asc");
*ext_pos = '\0';
} else if (strstr(temp_default, ".otp")) {
// Replace .otp with original extension or no extension
char* ext_pos = strstr(temp_default, ".otp");
*ext_pos = '\0';
} else {
// No recognized encrypted extension, add .decrypted suffix
strncat(temp_default, ".decrypted", sizeof(temp_default) - strlen(temp_default) - 1);
}
// Apply files directory default path
get_default_file_path(temp_default, default_output, sizeof(default_output));
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");
return 1;
}
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
char full_message[MAX_INPUT_SIZE * 4] = {0};
strcat(full_message, input_line);
strcat(full_message, "\n");
printf("Continue pasting the message (end with -----END OTP MESSAGE-----):\n");
char line[MAX_LINE_LENGTH];
while (fgets(line, sizeof(line), stdin)) {
strncat(full_message, line, sizeof(full_message) - strlen(full_message) - 1);
if (strstr(line, "-----END OTP MESSAGE-----")) {
break;
}
}
return decrypt_text(NULL, full_message);
}
else {
// Check if it looks like a file path
if (access(input_line, R_OK) == 0) {
// It's a valid file - decrypt it with enhanced input for output filename
char temp_default[512];
char default_output[512];
strncpy(temp_default, input_line, sizeof(temp_default) - 1);
temp_default[sizeof(temp_default) - 1] = '\0';
// Remove common encrypted extensions to get a better default
if (strstr(temp_default, ".otp.asc")) {
// Replace .otp.asc with original extension or no extension
char* ext_pos = strstr(temp_default, ".otp.asc");
*ext_pos = '\0';
} else if (strstr(temp_default, ".otp")) {
// Replace .otp with original extension or no extension
char* ext_pos = strstr(temp_default, ".otp");
*ext_pos = '\0';
} else {
// No recognized encrypted extension, add .decrypted suffix
strncat(temp_default, ".decrypted", sizeof(temp_default) - strlen(temp_default) - 1);
}
// Apply files directory default path
get_default_file_path(temp_default, default_output, sizeof(default_output));
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");
return 1;
}
return decrypt_file(input_line, output_file);
} else {
printf("Input not recognized as ASCII armor or valid file path.\n");
return 1;
}
}
}
uint64_t parse_size_string(const char* size_str) {
if (!size_str) return 0;
char* endptr;
double value = strtod(size_str, &endptr);
if (value <= 0) return 0;
// Skip whitespace
while (*endptr && isspace(*endptr)) endptr++;
uint64_t multiplier = 1;
if (*endptr) {
char unit[4];
strncpy(unit, endptr, 3);
unit[3] = '\0';
// Convert to uppercase
for (int i = 0; unit[i]; i++) {
unit[i] = toupper(unit[i]);
}
if (strcmp(unit, "K") == 0 || strcmp(unit, "KB") == 0) {
multiplier = 1024ULL;
} else if (strcmp(unit, "M") == 0 || strcmp(unit, "MB") == 0) {
multiplier = 1024ULL * 1024ULL;
} else if (strcmp(unit, "G") == 0 || strcmp(unit, "GB") == 0) {
multiplier = 1024ULL * 1024ULL * 1024ULL;
} else if (strcmp(unit, "T") == 0 || strcmp(unit, "TB") == 0) {
multiplier = 1024ULL * 1024ULL * 1024ULL * 1024ULL;
} else {
return 0; // Invalid unit
}
} else {
// No unit specified, treat as bytes
multiplier = 1;
}
return (uint64_t)(value * multiplier);
}
char* find_pad_by_prefix(const char* prefix) {
DIR* dir = opendir(current_pads_dir);
if (!dir) {
printf("Error: Cannot open pads directory %s\n", current_pads_dir);
return NULL;
}
struct dirent* entry;
char* matches[100]; // Store up to 100 matches
int match_count = 0;
// Always try hex prefix matching first
size_t prefix_len = strlen(prefix);
while ((entry = readdir(dir)) != NULL && match_count < 100) {
// Skip . and .. entries, and only process .pad files
if (entry->d_name[0] == '.') continue;
if (!strstr(entry->d_name, ".pad")) continue;
if (strlen(entry->d_name) != 68) continue; // 64 char chksum + ".pad"
// Compare prefix with the filename (checksum part)
if (strncmp(entry->d_name, prefix, prefix_len) == 0) {
matches[match_count] = malloc(65);
strncpy(matches[match_count], entry->d_name, 64);
matches[match_count][64] = '\0';
match_count++;
}
}
// Only prefix matching is supported
closedir(dir);
if (match_count == 0) {
printf("No pads found matching '%s'\n", prefix);
printf("Available pads:\n");
char* selected = select_pad_interactive("Available pads:", "Available pads (press Enter to continue)", PAD_FILTER_ALL, 0);
if (selected) {
free(selected);
}
return NULL;
} else if (match_count == 1) {
char* result = matches[0];
return result;
} else {
printf("Multiple matches found for '%s':\n", prefix);
for (int i = 0; i < match_count; i++) {
printf(" %.16s...\n", matches[i]);
}
printf("Please be more specific with your prefix.\n");
// Free all matches and return NULL
for (int i = 0; i < match_count; i++) {
free(matches[i]);
}
return NULL;
}
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// PADS MENU
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
int show_pad_info(const char* chksum) {
char pad_filename[MAX_HASH_LENGTH + 10];
char state_filename[MAX_HASH_LENGTH + 10];
snprintf(pad_filename, sizeof(pad_filename), "%s.pad", chksum);
snprintf(state_filename, sizeof(state_filename), "%s.state", chksum);
struct stat st;
if (stat(pad_filename, &st) != 0) {
printf("Pad not found: %s\n", chksum);
return 1;
}
uint64_t used_bytes;
read_state_offset(chksum, &used_bytes);
printf("=== Pad Information ===\n");
printf("ChkSum: %s\n", chksum);
printf("File: %s\n", pad_filename);
double size_gb = (double)st.st_size / (1024.0 * 1024.0 * 1024.0);
double used_gb = (double)used_bytes / (1024.0 * 1024.0 * 1024.0);
double remaining_gb = (double)(st.st_size - used_bytes) / (1024.0 * 1024.0 * 1024.0);
printf("Total size: %.2f GB (%lu bytes)\n", size_gb, st.st_size);
printf("Used: %.2f GB (%lu bytes)\n", used_gb, used_bytes);
printf("Remaining: %.2f GB (%lu bytes)\n", remaining_gb, st.st_size - used_bytes);
printf("Usage: %.1f%%\n", (double)used_bytes / st.st_size * 100.0);
return 0;
}
void show_progress(uint64_t current, uint64_t total, time_t start_time) {
time_t now = time(NULL);
double elapsed = difftime(now, start_time);
if (elapsed < 1.0) elapsed = 1.0; // Avoid division by zero
double percentage = (double)current / total * 100.0;
double speed = (double)current / elapsed / (1024.0 * 1024.0); // MB/s
uint64_t remaining_bytes = total - current;
double eta = remaining_bytes / (current / elapsed);
printf("\rProgress: %.1f%% (%.1f MB/s, ETA: %.0fs) ", percentage, speed, eta);
fflush(stdout);
}
int generate_pad(uint64_t size_bytes, int display_progress) {
// Ensure pads directory exists
if (ensure_pads_directory() != 0) {
printf("Error: Cannot create pads directory\n");
return 1;
}
char temp_filename[64];
char pad_path[MAX_HASH_LENGTH + 20];
char state_path[MAX_HASH_LENGTH + 20];
char chksum_hex[MAX_HASH_LENGTH];
// Create temporary filename
snprintf(temp_filename, sizeof(temp_filename), "temp_%ld.pad", time(NULL));
FILE* urandom = fopen("/dev/urandom", "rb");
if (!urandom) {
printf("Error: Cannot open /dev/urandom\n");
return 1;
}
FILE* pad_file = fopen(temp_filename, "wb");
if (!pad_file) {
printf("Error: Cannot create temporary pad file %s\n", temp_filename);
fclose(urandom);
return 1;
}
unsigned char buffer[64 * 1024]; // 64KB buffer
uint64_t bytes_written = 0;
time_t start_time = time(NULL);
if (display_progress) {
printf("Generating pad...\n");
}
while (bytes_written < size_bytes) {
uint64_t chunk_size = sizeof(buffer);
if (size_bytes - bytes_written < chunk_size) {
chunk_size = size_bytes - bytes_written;
}
if (fread(buffer, 1, (size_t)chunk_size, urandom) != (size_t)chunk_size) {
printf("Error: Failed to read from /dev/urandom\n");
fclose(urandom);
fclose(pad_file);
unlink(temp_filename);
return 1;
}
if (fwrite(buffer, 1, (size_t)chunk_size, pad_file) != (size_t)chunk_size) {
printf("Error: Failed to write to pad file\n");
fclose(urandom);
fclose(pad_file);
unlink(temp_filename);
return 1;
}
bytes_written += chunk_size;
if (display_progress && bytes_written % PROGRESS_UPDATE_INTERVAL == 0) {
show_progress(bytes_written, size_bytes, start_time);
}
}
if (display_progress) {
show_progress(size_bytes, size_bytes, start_time);
printf("\n");
}
fclose(urandom);
fclose(pad_file);
// Calculate XOR checksum of the pad file
if (calculate_checksum(temp_filename, chksum_hex) != 0) {
printf("Error: Cannot calculate pad checksum\n");
unlink(temp_filename);
return 1;
}
// Get final paths in pads directory
get_pad_path(chksum_hex, pad_path, state_path);
// Try rename first (works for same filesystem)
if (rename(temp_filename, pad_path) != 0) {
// If rename fails, try copy and delete (works across filesystems)
FILE* temp_file = fopen(temp_filename, "rb");
FILE* dest_file = fopen(pad_path, "wb");
if (!temp_file || !dest_file) {
printf("Error: Cannot copy pad file to pads directory\n");
if (temp_file) fclose(temp_file);
if (dest_file) fclose(dest_file);
unlink(temp_filename);
return 1;
}
// Copy file in chunks
unsigned char copy_buffer[64 * 1024];
size_t bytes_read;
while ((bytes_read = fread(copy_buffer, 1, sizeof(copy_buffer), temp_file)) > 0) {
if (fwrite(copy_buffer, 1, bytes_read, dest_file) != bytes_read) {
printf("Error: Failed to copy pad file to pads directory\n");
fclose(temp_file);
fclose(dest_file);
unlink(temp_filename);
unlink(pad_path);
return 1;
}
}
fclose(temp_file);
fclose(dest_file);
// Remove temporary file after successful copy
unlink(temp_filename);
}
// Set pad file to read-only
if (chmod(pad_path, S_IRUSR) != 0) {
printf("Warning: Cannot set pad file to read-only\n");
}
// Initialize state file with offset 32 (first 32 bytes reserved for checksum encryption)
FILE* state_file = fopen(state_path, "wb");
if (state_file) {
uint64_t reserved_bytes = 32;
fwrite(&reserved_bytes, sizeof(uint64_t), 1, state_file);
fclose(state_file);
} else {
printf("Error: Failed to create state file\n");
unlink(pad_path);
return 1;
}
double size_gb = (double)size_bytes / (1024.0 * 1024.0 * 1024.0);
printf("Generated pad: %s (%.2f GB)\n", pad_path, size_gb);
printf("Pad checksum: %s\n", chksum_hex);
printf("State file: %s\n", state_path);
printf("Pad file set to read-only\n");
printf("Use 'Add entropy' in Pads menu to enhance randomness.\n");
return 0;
}
// In-place pad entropy addition using Chacha20
int add_entropy_to_pad(const char* pad_chksum, const unsigned char* entropy_data,
size_t entropy_size, int display_progress) {
if (!pad_chksum || !entropy_data || entropy_size < 512) {
printf("Error: Invalid entropy data or insufficient entropy\n");
return 1;
}
// Derive Chacha20 key and nonce from entropy
unsigned char key[32], nonce[12];
if (derive_chacha20_params(entropy_data, entropy_size, key, nonce) != 0) {
printf("Error: Failed to derive Chacha20 parameters from entropy\n");
return 1;
}
// Get pad file path
char pad_path[1024];
char state_path[1024];
get_pad_path(pad_chksum, pad_path, state_path);
// Check if pad exists and get size
struct stat pad_stat;
if (stat(pad_path, &pad_stat) != 0) {
printf("Error: Pad file not found: %s\n", pad_path);
return 1;
}
uint64_t pad_size = pad_stat.st_size;
// Open pad file for read/write
FILE* pad_file = fopen(pad_path, "r+b");
if (!pad_file) {
printf("Error: Cannot open pad file for modification: %s\n", pad_path);
printf("Note: Pad files are read-only. Temporarily changing permissions...\n");
// Try to make writable temporarily
if (chmod(pad_path, S_IRUSR | S_IWUSR) != 0) {
printf("Error: Cannot change pad file permissions\n");
return 1;
}
pad_file = fopen(pad_path, "r+b");
if (!pad_file) {
printf("Error: Still cannot open pad file for modification\n");
// Restore read-only
chmod(pad_path, S_IRUSR);
return 1;
}
}
if (display_progress) {
printf("Adding entropy to pad using Chacha20...\n");
printf("Pad size: %.2f GB (%lu bytes)\n", (double)pad_size / (1024.0*1024.0*1024.0), pad_size);
}
// Process pad in chunks
unsigned char buffer[64 * 1024]; // 64KB chunks
unsigned char keystream[64 * 1024];
uint64_t offset = 0;
uint32_t counter = 0;
time_t start_time = time(NULL);
while (offset < pad_size) {
size_t chunk_size = sizeof(buffer);
if (pad_size - offset < chunk_size) {
chunk_size = pad_size - offset;
}
// Read current pad data
if (fread(buffer, 1, chunk_size, pad_file) != chunk_size) {
printf("Error: Cannot read pad data at offset %lu\n", offset);
fclose(pad_file);
chmod(pad_path, S_IRUSR); // Restore read-only
return 1;
}
// Generate keystream for this chunk
if (chacha20_encrypt(key, counter, nonce, buffer, keystream, chunk_size) != 0) {
printf("Error: Chacha20 keystream generation failed\n");
fclose(pad_file);
chmod(pad_path, S_IRUSR);
return 1;
}
// XOR existing pad with keystream (adds entropy)
for (size_t i = 0; i < chunk_size; i++) {
buffer[i] ^= keystream[i];
}
// Seek back and write modified data
if (fseek(pad_file, offset, SEEK_SET) != 0) {
printf("Error: Cannot seek to offset %lu\n", offset);
fclose(pad_file);
chmod(pad_path, S_IRUSR);
return 1;
}
if (fwrite(buffer, 1, chunk_size, pad_file) != chunk_size) {
printf("Error: Cannot write modified pad data\n");
fclose(pad_file);
chmod(pad_path, S_IRUSR);
return 1;
}
offset += chunk_size;
counter += (chunk_size + 63) / 64; // Round up for block count
// Show progress for large pads
if (display_progress && offset % (64 * 1024 * 1024) == 0) { // Every 64MB
show_progress(offset, pad_size, start_time);
}
}
fclose(pad_file);
// Restore read-only permissions
if (chmod(pad_path, S_IRUSR) != 0) {
printf("Warning: Cannot restore pad file to read-only\n");
}
if (display_progress) {
show_progress(pad_size, pad_size, start_time);
printf("\n✓ Entropy successfully added to pad using Chacha20\n");
printf("✓ Pad integrity maintained\n");
printf("✓ %zu bytes of entropy distributed across entire pad\n", entropy_size);
printf("✓ Pad restored to read-only mode\n");
// Update checksum after entropy addition
printf("\n🔄 Updating pad checksum...\n");
char new_chksum[65];
int checksum_result = update_pad_checksum_after_entropy(pad_chksum, new_chksum);
if (checksum_result == 0) {
printf("✓ Pad checksum updated successfully\n");
printf(" Old checksum: %.16s...\n", pad_chksum);
printf(" New checksum: %.16s...\n", new_chksum);
printf("✓ Pad files renamed to new checksum\n");
} else if (checksum_result == 2) {
printf(" Checksum unchanged (unusual but not an error)\n");
} else {
printf("⚠ Warning: Checksum update failed (entropy was added successfully)\n");
printf(" You may need to manually handle the checksum update\n");
return 1; // Report error despite successful entropy addition
}
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// ENCRYPT AND DECRYPT
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
int encrypt_text(const char* pad_identifier, const char* input_text) {
char* pad_chksum = find_pad_by_prefix(pad_identifier);
if (!pad_chksum) {
return 1;
}
char text_buffer[MAX_INPUT_SIZE];
char chksum_hex[MAX_HASH_LENGTH];
uint64_t current_offset;
char pad_path[MAX_HASH_LENGTH + 20];
char state_path[MAX_HASH_LENGTH + 20];
get_pad_path(pad_chksum, pad_path, state_path);
// Check if pad file exists
if (access(pad_path, R_OK) != 0) {
printf("Error: Pad file %s not found\n", pad_path);
free(pad_chksum);
return 1;
}
// Read current offset
if (read_state_offset(pad_chksum, &current_offset) != 0) {
printf("Error: Cannot read state file\n");
free(pad_chksum);
return 1;
}
// Ensure we never encrypt before offset 32 (reserved for checksum encryption)
if (current_offset < 32) {
printf("Warning: State offset below reserved area, adjusting to 32\n");
current_offset = 32;
if (write_state_offset(pad_chksum, current_offset) != 0) {
printf("Warning: Failed to update state file\n");
}
}
// Calculate XOR checksum of pad file
if (calculate_checksum(pad_path, chksum_hex) != 0) {
printf("Error: Cannot calculate pad checksum\n");
free(pad_chksum);
return 1;
}
// Get input text - either from parameter or user input
if (input_text != NULL) {
// Use provided text
strncpy(text_buffer, input_text, sizeof(text_buffer) - 1);
text_buffer[sizeof(text_buffer) - 1] = '\0';
} else {
// Get input text from user (interactive mode)
if (is_interactive_mode) {
printf("\nText input options:\n");
printf(" 1. Type text directly\n");
printf(" 2. Use text editor\n");
printf("Enter choice (1-2): ");
}
char input_choice[10] = "1"; // Default to direct input in non-interactive mode
if (is_interactive_mode) {
if (!fgets(input_choice, sizeof(input_choice), stdin)) {
printf("Error: Failed to read input\n");
free(pad_chksum);
return 1;
}
}
if (is_interactive_mode && atoi(input_choice) == 2) {
// Use text editor
if (launch_text_editor(NULL, text_buffer, sizeof(text_buffer)) != 0) {
if (is_interactive_mode) {
printf("Falling back to direct text input.\n");
printf("Enter text to encrypt: ");
}
fflush(stdout);
if (fgets(text_buffer, sizeof(text_buffer), stdin) == NULL) {
printf("Error: Failed to read input\n");
free(pad_chksum);
return 1;
}
// Remove newline if present
size_t len = strlen(text_buffer);
if (len > 0 && text_buffer[len - 1] == '\n') {
text_buffer[len - 1] = '\0';
}
}
} else {
// Direct text input
if (is_interactive_mode) {
printf("Enter text to encrypt: ");
fflush(stdout);
}
if (fgets(text_buffer, sizeof(text_buffer), stdin) == NULL) {
printf("Error: Failed to read input\n");
free(pad_chksum);
return 1;
}
// Remove newline if present
size_t len = strlen(text_buffer);
if (len > 0 && text_buffer[len - 1] == '\n') {
text_buffer[len - 1] = '\0';
}
}
}
size_t input_len = strlen(text_buffer);
if (input_len == 0) {
printf("Error: No input provided\n");
free(pad_chksum);
return 1;
}
// Check if we have enough pad space
struct stat pad_stat;
if (stat(pad_path, &pad_stat) != 0) {
printf("Error: Cannot get pad file size\n");
free(pad_chksum);
return 1;
}
if (current_offset + input_len > (uint64_t)pad_stat.st_size) {
printf("Error: Not enough pad space remaining\n");
printf("Need: %lu bytes, Available: %lu bytes\n",
input_len, (uint64_t)pad_stat.st_size - current_offset);
free(pad_chksum);
return 1;
}
// Read pad data at current offset
FILE* pad_file = fopen(pad_path, "rb");
if (!pad_file) {
printf("Error: Cannot open pad file\n");
free(pad_chksum);
return 1;
}
if (fseek(pad_file, current_offset, SEEK_SET) != 0) {
printf("Error: Cannot seek to offset in pad file\n");
fclose(pad_file);
free(pad_chksum);
return 1;
}
unsigned char* pad_data = malloc(input_len);
if (fread(pad_data, 1, input_len, pad_file) != input_len) {
printf("Error: Cannot read pad data\n");
free(pad_data);
fclose(pad_file);
free(pad_chksum);
return 1;
}
fclose(pad_file);
// Use universal XOR operation for encryption
unsigned char* ciphertext = malloc(input_len);
if (universal_xor_operation((const unsigned char*)text_buffer, input_len, pad_data, ciphertext) != 0) {
printf("Error: Encryption operation failed\n");
free(pad_data);
free(ciphertext);
free(pad_chksum);
return 1;
}
// Update state offset
if (write_state_offset(pad_chksum, current_offset + input_len) != 0) {
printf("Warning: Failed to update state file\n");
}
// Use universal ASCII armor generator
char* ascii_output;
if (generate_ascii_armor(chksum_hex, current_offset, ciphertext, input_len, &ascii_output) != 0) {
printf("Error: Failed to generate ASCII armor\n");
free(pad_data);
free(ciphertext);
free(pad_chksum);
return 1;
}
// Output with appropriate formatting - clean format for piping, spaced format for interactive
int is_interactive = (input_text == NULL); // Interactive if no input_text provided
if (is_interactive) {
printf("\n\n\n%s\n\n", ascii_output);
} else {
printf("%s\n", ascii_output); // Add newline for proper piping with tee
}
// Cleanup
free(pad_data);
free(ciphertext);
free(ascii_output);
free(pad_chksum);
return 0;
}
int decrypt_text(const char* pad_identifier, const char* encrypted_message) {
// Use universal decrypt function with mode based on global interactive mode detection
(void)pad_identifier; // Suppress unused parameter warning - chksum comes from message
decrypt_mode_t mode = is_interactive_mode ? DECRYPT_MODE_INTERACTIVE : DECRYPT_MODE_SILENT;
return universal_decrypt(encrypted_message, NULL, mode);
}
int encrypt_file(const char* pad_identifier, const char* input_file, const char* output_file, int ascii_armor) {
char* pad_chksum = find_pad_by_prefix(pad_identifier);
if (!pad_chksum) {
return 1;
}
char chksum_hex[MAX_HASH_LENGTH];
uint64_t current_offset;
char pad_path[MAX_HASH_LENGTH + 20];
char state_path[MAX_HASH_LENGTH + 20];
get_pad_path(pad_chksum, pad_path, state_path);
// Check if input file exists and get its size
struct stat input_stat;
if (stat(input_file, &input_stat) != 0) {
printf("Error: Input file %s not found\n", input_file);
free(pad_chksum);
return 1;
}
uint64_t file_size = input_stat.st_size;
if (file_size == 0) {
printf("Error: Input file is empty\n");
free(pad_chksum);
return 1;
}
// Check if pad file exists
if (access(pad_path, R_OK) != 0) {
printf("Error: Pad file %s not found\n", pad_path);
free(pad_chksum);
return 1;
}
// Read current offset
if (read_state_offset(pad_chksum, &current_offset) != 0) {
printf("Error: Cannot read state file\n");
free(pad_chksum);
return 1;
}
// Ensure we never encrypt before offset 32
if (current_offset < 32) {
printf("Warning: State offset below reserved area, adjusting to 32\n");
current_offset = 32;
if (write_state_offset(pad_chksum, current_offset) != 0) {
printf("Warning: Failed to update state file\n");
}
}
// Calculate XOR checksum of pad file
if (calculate_checksum(pad_path, chksum_hex) != 0) {
printf("Error: Cannot calculate pad checksum\n");
free(pad_chksum);
return 1;
}
// Check if we have enough pad space
struct stat pad_stat;
if (stat(pad_path, &pad_stat) != 0) {
printf("Error: Cannot get pad file size\n");
free(pad_chksum);
return 1;
}
if (current_offset + file_size > (uint64_t)pad_stat.st_size) {
printf("Error: Not enough pad space remaining\n");
printf("Need: %lu bytes, Available: %lu bytes\n",
file_size, (uint64_t)pad_stat.st_size - current_offset);
free(pad_chksum);
return 1;
}
// Generate output filename if not specified, using files directory
char default_output[512];
if (output_file == NULL) {
char temp_output[512];
if (ascii_armor) {
snprintf(temp_output, sizeof(temp_output), "%s.otp.asc", input_file);
} else {
snprintf(temp_output, sizeof(temp_output), "%s.otp", input_file);
}
// Apply files directory default path
get_default_file_path(temp_output, default_output, sizeof(default_output));
output_file = default_output;
}
// Open input file
FILE* input_fp = fopen(input_file, "rb");
if (!input_fp) {
printf("Error: Cannot open input file %s\n", input_file);
free(pad_chksum);
return 1;
}
// Open pad file
FILE* pad_file = fopen(pad_path, "rb");
if (!pad_file) {
printf("Error: Cannot open pad file\n");
fclose(input_fp);
free(pad_chksum);
return 1;
}
if (fseek(pad_file, current_offset, SEEK_SET) != 0) {
printf("Error: Cannot seek to offset in pad file\n");
fclose(input_fp);
fclose(pad_file);
free(pad_chksum);
return 1;
}
// Read and encrypt file
unsigned char buffer[64 * 1024];
unsigned char pad_buffer[64 * 1024];
unsigned char* encrypted_data = malloc(file_size);
uint64_t bytes_processed = 0;
time_t start_time = time(NULL);
printf("Encrypting %s...\n", input_file);
while (bytes_processed < file_size) {
uint64_t chunk_size = sizeof(buffer);
if (file_size - bytes_processed < chunk_size) {
chunk_size = file_size - bytes_processed;
}
// Read file data
if (fread(buffer, 1, chunk_size, input_fp) != chunk_size) {
printf("Error: Cannot read input file data\n");
free(encrypted_data);
fclose(input_fp);
fclose(pad_file);
free(pad_chksum);
return 1;
}
// Read pad data
if (fread(pad_buffer, 1, chunk_size, pad_file) != chunk_size) {
printf("Error: Cannot read pad data\n");
free(encrypted_data);
fclose(input_fp);
fclose(pad_file);
free(pad_chksum);
return 1;
}
// Use universal XOR operation for encryption
if (universal_xor_operation(buffer, chunk_size, pad_buffer, &encrypted_data[bytes_processed]) != 0) {
printf("Error: Encryption operation failed\n");
free(encrypted_data);
fclose(input_fp);
fclose(pad_file);
free(pad_chksum);
return 1;
}
bytes_processed += chunk_size;
// Show progress for large files (> 10MB)
if (file_size > 10 * 1024 * 1024 && bytes_processed % (1024 * 1024) == 0) {
show_progress(bytes_processed, file_size, start_time);
}
}
if (file_size > 10 * 1024 * 1024) {
show_progress(file_size, file_size, start_time);
printf("\n");
}
fclose(input_fp);
fclose(pad_file);
// Write output file
if (ascii_armor) {
// ASCII armored format - same as message format
FILE* output_fp = fopen(output_file, "w");
if (!output_fp) {
printf("Error: Cannot create output file %s\n", output_file);
free(encrypted_data);
free(pad_chksum);
return 1;
}
// Use universal ASCII armor generator
char* ascii_output;
if (generate_ascii_armor(chksum_hex, current_offset, encrypted_data, file_size, &ascii_output) != 0) {
printf("Error: Failed to generate ASCII armor\n");
fclose(output_fp);
free(encrypted_data);
free(pad_chksum);
return 1;
}
// Write the ASCII armored output to file
fprintf(output_fp, "%s", ascii_output);
fclose(output_fp);
free(ascii_output);
} else {
// Binary format
FILE* output_fp = fopen(output_file, "wb");
if (!output_fp) {
printf("Error: Cannot create output file %s\n", output_file);
free(encrypted_data);
free(pad_chksum);
return 1;
}
// Write binary header
// Magic: "OTP\0"
fwrite("OTP\0", 1, 4, output_fp);
// Version: 2 bytes
uint16_t version = 1;
fwrite(&version, sizeof(uint16_t), 1, output_fp);
// Pad checksum: 32 bytes (binary)
unsigned char pad_chksum_bin[32];
for (int i = 0; i < 32; i++) {
sscanf(chksum_hex + i*2, "%2hhx", &pad_chksum_bin[i]);
}
fwrite(pad_chksum_bin, 1, 32, output_fp);
// Pad offset: 8 bytes
fwrite(&current_offset, sizeof(uint64_t), 1, output_fp);
// File mode: 4 bytes
uint32_t file_mode = input_stat.st_mode;
fwrite(&file_mode, sizeof(uint32_t), 1, output_fp);
// File size: 8 bytes
fwrite(&file_size, sizeof(uint64_t), 1, output_fp);
// Encrypted data
fwrite(encrypted_data, 1, file_size, output_fp);
fclose(output_fp);
}
// Update state offset
if (write_state_offset(pad_chksum, current_offset + file_size) != 0) {
printf("Warning: Failed to update state file\n");
}
printf("File encrypted successfully: %s\n", output_file);
if (ascii_armor) {
printf("Format: ASCII armored (.otp.asc)\n");
} else {
printf("Format: Binary (.otp)\n");
}
// Cleanup
free(encrypted_data);
free(pad_chksum);
return 0;
}
int decrypt_file(const char* input_file, const char* output_file) {
// Check if input file exists
if (access(input_file, R_OK) != 0) {
printf("Error: Input file %s not found\n", input_file);
return 1;
}
FILE* input_fp = fopen(input_file, "rb");
if (!input_fp) {
printf("Error: Cannot open input file %s\n", input_file);
return 1;
}
// Read first few bytes to determine format
char magic[4];
if (fread(magic, 1, 4, input_fp) != 4) {
printf("Error: Cannot read file header\n");
fclose(input_fp);
return 1;
}
fseek(input_fp, 0, SEEK_SET); // Reset to beginning
if (memcmp(magic, "OTP\0", 4) == 0) {
// Binary format
return decrypt_binary_file(input_fp, output_file);
} else {
// Assume ASCII armored format, read entire file as text
fclose(input_fp);
return decrypt_ascii_file(input_file, output_file);
}
}
int decrypt_binary_file(FILE* input_fp, const char* output_file) {
// Read binary header
char magic[4];
uint16_t version;
unsigned char pad_chksum_bin[32];
uint64_t pad_offset;
uint32_t file_mode;
uint64_t file_size;
if (fread(magic, 1, 4, input_fp) != 4 ||
fread(&version, sizeof(uint16_t), 1, input_fp) != 1 ||
fread(pad_chksum_bin, 1, 32, input_fp) != 32 ||
fread(&pad_offset, sizeof(uint64_t), 1, input_fp) != 1 ||
fread(&file_mode, sizeof(uint32_t), 1, input_fp) != 1 ||
fread(&file_size, sizeof(uint64_t), 1, input_fp) != 1) {
printf("Error: Cannot read binary header\n");
fclose(input_fp);
return 1;
}
if (memcmp(magic, "OTP\0", 4) != 0) {
printf("Error: Invalid binary format\n");
fclose(input_fp);
return 1;
}
// Convert binary checksum to hex
char pad_chksum_hex[65];
for (int i = 0; i < 32; i++) {
sprintf(pad_chksum_hex + i*2, "%02x", pad_chksum_bin[i]);
}
pad_chksum_hex[64] = '\0';
printf("Decrypting binary file...\n");
printf("File size: %lu bytes\n", file_size);
// Check if we have the required pad
char pad_path[MAX_HASH_LENGTH + 20];
char state_path[MAX_HASH_LENGTH + 20];
get_pad_path(pad_chksum_hex, pad_path, state_path);
if (access(pad_path, R_OK) != 0) {
printf("Error: Required pad not found: %s\n", pad_chksum_hex);
printf("Available pads:\n");
char* selected = select_pad_interactive("Available pads:", "Available pads (press Enter to continue)", PAD_FILTER_ALL, 0);
if (selected) {
free(selected);
}
fclose(input_fp);
return 1;
}
// Determine output filename
char default_output[512];
if (output_file == NULL) {
snprintf(default_output, sizeof(default_output), "decrypted.bin");
output_file = default_output;
}
// Read encrypted data
unsigned char* encrypted_data = malloc(file_size);
if (fread(encrypted_data, 1, file_size, input_fp) != file_size) {
printf("Error: Cannot read encrypted data\n");
free(encrypted_data);
fclose(input_fp);
return 1;
}
fclose(input_fp);
// Open pad file and decrypt
FILE* pad_file = fopen(pad_path, "rb");
if (!pad_file) {
printf("Error: Cannot open pad file\n");
free(encrypted_data);
return 1;
}
if (fseek(pad_file, pad_offset, SEEK_SET) != 0) {
printf("Error: Cannot seek to offset in pad file\n");
free(encrypted_data);
fclose(pad_file);
return 1;
}
unsigned char* pad_data = malloc(file_size);
if (fread(pad_data, 1, file_size, pad_file) != file_size) {
printf("Error: Cannot read pad data\n");
free(encrypted_data);
free(pad_data);
fclose(pad_file);
return 1;
}
fclose(pad_file);
// Use universal XOR operation for decryption
if (universal_xor_operation(encrypted_data, file_size, pad_data, encrypted_data) != 0) {
printf("Error: Decryption operation failed\n");
free(encrypted_data);
free(pad_data);
return 1;
}
// Write decrypted file
FILE* output_fp = fopen(output_file, "wb");
if (!output_fp) {
printf("Error: Cannot create output file %s\n", output_file);
free(encrypted_data);
free(pad_data);
return 1;
}
if (fwrite(encrypted_data, 1, file_size, output_fp) != file_size) {
printf("Error: Cannot write decrypted data\n");
free(encrypted_data);
free(pad_data);
fclose(output_fp);
return 1;
}
fclose(output_fp);
// Restore file permissions
if (chmod(output_file, file_mode) != 0) {
printf("Warning: Cannot restore file permissions\n");
}
printf("File decrypted successfully: %s\n", output_file);
printf("Restored permissions and metadata\n");
// Cleanup
free(encrypted_data);
free(pad_data);
return 0;
}
int decrypt_ascii_file(const char* input_file, const char* output_file) {
// Use universal decrypt function with file-to-file mode
return universal_decrypt(input_file, output_file, DECRYPT_MODE_FILE_TO_FILE);
}
int read_state_offset(const char* pad_chksum, uint64_t* offset) {
char state_filename[1024];
snprintf(state_filename, sizeof(state_filename), "%s/%s.state", current_pads_dir, pad_chksum);
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;
}
int write_state_offset(const char* pad_chksum, uint64_t offset) {
char state_filename[1024];
snprintf(state_filename, sizeof(state_filename), "%s/%s.state", current_pads_dir, pad_chksum);
FILE* state_file = fopen(state_filename, "wb");
if (!state_file) {
return 1;
}
if (fwrite(&offset, sizeof(uint64_t), 1, state_file) != 1) {
fclose(state_file);
return 1;
}
fclose(state_file);
return 0;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// DIRECTORY MANAGEMENT FUNCTIONS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
int ensure_pads_directory(void) {
struct stat st = {0};
if (stat(current_pads_dir, &st) == -1) {
if (mkdir(current_pads_dir, 0755) != 0) {
return 1;
}
}
return 0;
}
const char* get_files_directory(void) {
struct stat st = {0};
if (stat(FILES_DIR, &st) == 0 && S_ISDIR(st.st_mode)) {
return FILES_DIR;
}
return "."; // Fall back to current directory
}
void get_default_file_path(const char* filename, char* result_path, size_t result_size) {
const char* files_dir = get_files_directory();
// If filename already has a path (contains '/'), use it as-is
if (strchr(filename, '/') != NULL) {
strncpy(result_path, filename, result_size - 1);
result_path[result_size - 1] = '\0';
return;
}
// Otherwise, prepend the files directory
snprintf(result_path, result_size, "%s/%s", files_dir, filename);
}
void get_pad_path(const char* chksum, char* pad_path, char* state_path) {
snprintf(pad_path, 1024, "%s/%s.pad", current_pads_dir, chksum);
snprintf(state_path, 1024, "%s/%s.state", current_pads_dir, chksum);
}
// Stdin detection functions implementation
int has_stdin_data(void) {
// Check if stdin is a pipe/redirect (not a terminal)
if (!isatty(STDIN_FILENO)) {
return 1;
}
return 0;
}
char* read_stdin_text(void) {
size_t capacity = 4096;
size_t length = 0;
char* buffer = malloc(capacity);
if (!buffer) {
return NULL;
}
char chunk[1024];
while (fgets(chunk, sizeof(chunk), stdin)) {
size_t chunk_len = strlen(chunk);
// Ensure we have enough capacity
while (length + chunk_len >= capacity) {
capacity *= 2;
char* new_buffer = realloc(buffer, capacity);
if (!new_buffer) {
free(buffer);
return NULL;
}
buffer = new_buffer;
}
strcpy(buffer + length, chunk);
length += chunk_len;
}
// Remove trailing newline if present
if (length > 0 && buffer[length - 1] == '\n') {
buffer[length - 1] = '\0';
length--;
}
// If empty, free and return NULL
if (length == 0) {
free(buffer);
return NULL;
}
return buffer;
}
int pipe_mode(int argc, char* argv[], const char* piped_text) {
(void)argc; // Suppress unused parameter warning
(void)argv; // Suppress unused parameter warning
// Check if we have a default pad configured
char* default_pad = get_default_pad_path();
if (default_pad) {
// Verify the default pad exists and extract checksum
if (access(default_pad, R_OK) == 0) {
// Extract checksum from pad filename
char* filename = strrchr(default_pad, '/');
if (!filename) filename = default_pad;
else filename++; // Skip the '/'
// Extract checksum (remove .pad extension)
if (strlen(filename) >= 68 && strstr(filename, ".pad")) {
char pad_checksum[65];
strncpy(pad_checksum, filename, 64);
pad_checksum[64] = '\0';
free(default_pad);
// Encrypt using the default pad (silent mode)
return encrypt_text(pad_checksum, piped_text);
}
}
fprintf(stderr, "Error: Default pad not found or invalid: %s\n", default_pad);
free(default_pad);
return 1;
}
fprintf(stderr, "Error: No default pad configured for pipe mode\n");
fprintf(stderr, "Configure a default pad in ~/.otp/otp.conf\n");
return 1;
}
// Preferences management functions implementation
int load_preferences(void) {
char* home_dir = getenv("HOME");
if (!home_dir) {
return 1; // No home directory
}
char preferences_dir[1024];
char preferences_file[2048]; // Increased buffer size to accommodate longer paths
snprintf(preferences_dir, sizeof(preferences_dir), "%s/.otp", home_dir);
snprintf(preferences_file, sizeof(preferences_file), "%s/otp.conf", preferences_dir);
FILE* file = fopen(preferences_file, "r");
if (!file) {
// No preferences file exists - create it and set first pad as default
// Create .otp directory if it doesn't exist
struct stat st = {0};
if (stat(preferences_dir, &st) == -1) {
if (mkdir(preferences_dir, 0755) != 0) {
return 1;
}
}
// Find the first available pad to set as default
DIR* dir = opendir(current_pads_dir);
if (dir) {
struct dirent* entry;
char first_pad_path[1024];
int found_pad = 0;
while ((entry = readdir(dir)) != NULL && !found_pad) {
if (strstr(entry->d_name, ".pad") && strlen(entry->d_name) == 68) {
// Found a pad file - construct full absolute path
if (current_pads_dir[0] == '/') {
// Already absolute path
int ret = snprintf(first_pad_path, sizeof(first_pad_path), "%s/%s", current_pads_dir, entry->d_name);
if (ret >= (int)sizeof(first_pad_path)) {
// Path was truncated, skip this entry
continue;
}
} else {
// Relative path - make it absolute
char current_dir[512];
if (getcwd(current_dir, sizeof(current_dir))) {
int ret = snprintf(first_pad_path, sizeof(first_pad_path), "%s/%s/%s", current_dir, current_pads_dir, entry->d_name);
if (ret >= (int)sizeof(first_pad_path)) {
// Path was truncated, skip this entry
continue;
}
} else {
// Fallback to relative path
int ret = snprintf(first_pad_path, sizeof(first_pad_path), "%s/%s", current_pads_dir, entry->d_name);
if (ret >= (int)sizeof(first_pad_path)) {
// Path was truncated, skip this entry
continue;
}
}
}
strncpy(default_pad_path, first_pad_path, sizeof(default_pad_path) - 1);
default_pad_path[sizeof(default_pad_path) - 1] = '\0';
found_pad = 1;
}
}
closedir(dir);
// Create the preferences file with the default pad
if (found_pad) {
save_preferences();
}
}
return 0; // Successfully initialized
}
char line[1024];
while (fgets(line, sizeof(line), file)) {
// Remove newline
line[strcspn(line, "\n")] = 0;
// Skip empty lines and comments
if (strlen(line) == 0 || line[0] == '#') {
continue;
}
// Parse key=value pairs
char* equals = strchr(line, '=');
if (equals) {
*equals = '\0';
char* key = line;
char* value = equals + 1;
// Trim whitespace
while (*key == ' ' || *key == '\t') key++;
while (*value == ' ' || *value == '\t') value++;
if (strcmp(key, "default_pad") == 0) {
strncpy(default_pad_path, value, sizeof(default_pad_path) - 1);
default_pad_path[sizeof(default_pad_path) - 1] = '\0';
}
}
}
fclose(file);
return 0;
}
int save_preferences(void) {
char* home_dir = getenv("HOME");
if (!home_dir) {
return 1;
}
char preferences_dir[1024];
char preferences_file[2048]; // Increased buffer size to accommodate longer paths
snprintf(preferences_dir, sizeof(preferences_dir), "%s/.otp", home_dir);
snprintf(preferences_file, sizeof(preferences_file), "%s/otp.conf", preferences_dir);
// Create .otp directory if it doesn't exist
struct stat st = {0};
if (stat(preferences_dir, &st) == -1) {
if (mkdir(preferences_dir, 0755) != 0) {
return 1;
}
}
FILE* file = fopen(preferences_file, "w");
if (!file) {
return 1;
}
fprintf(file, "# OTP Preferences File\n");
fprintf(file, "# This file is automatically generated and updated by the OTP program\n\n");
if (strlen(default_pad_path) > 0) {
fprintf(file, "default_pad=%s\n", default_pad_path);
}
fclose(file);
return 0;
}
char* get_preference(const char* key) {
if (strcmp(key, "default_pad") == 0) {
if (strlen(default_pad_path) > 0) {
return strdup(default_pad_path);
}
}
return NULL;
}
int set_preference(const char* key, const char* value) {
if (strcmp(key, "default_pad") == 0) {
if (value) {
strncpy(default_pad_path, value, sizeof(default_pad_path) - 1);
default_pad_path[sizeof(default_pad_path) - 1] = '\0';
} else {
default_pad_path[0] = '\0';
}
return save_preferences();
}
return 1;
}
char* get_default_pad_path(void) {
if (strlen(default_pad_path) > 0) {
return strdup(default_pad_path);
}
return NULL;
}
int set_default_pad_path(const char* pad_path) {
if (!pad_path) {
return set_preference("default_pad", NULL);
}
// Ensure we store the full absolute path
char absolute_path[1024];
if (pad_path[0] == '/') {
// Already absolute path
strncpy(absolute_path, pad_path, sizeof(absolute_path) - 1);
absolute_path[sizeof(absolute_path) - 1] = '\0';
} else {
// Relative path - make it absolute
char current_dir[512];
if (getcwd(current_dir, sizeof(current_dir))) {
snprintf(absolute_path, sizeof(absolute_path), "%s/%s", current_dir, pad_path);
} else {
// Fallback to using the path as-is if getcwd fails
strncpy(absolute_path, pad_path, sizeof(absolute_path) - 1);
absolute_path[sizeof(absolute_path) - 1] = '\0';
}
}
return set_preference("default_pad", absolute_path);
}
// OTP thumb drive detection function implementation
int detect_otp_thumb_drive(char* otp_drive_path, size_t path_size) {
const char* mount_dirs[] = {"/media", "/run/media", "/mnt", NULL};
for (int mount_idx = 0; mount_dirs[mount_idx] != NULL; mount_idx++) {
DIR* mount_dir = opendir(mount_dirs[mount_idx]);
if (!mount_dir) continue;
struct dirent* mount_entry;
while ((mount_entry = readdir(mount_dir)) != NULL) {
if (mount_entry->d_name[0] == '.') continue;
char mount_path[1024]; // Increased buffer size
snprintf(mount_path, sizeof(mount_path), "%s/%s", mount_dirs[mount_idx], mount_entry->d_name);
// For /media, we need to go one level deeper (user directories)
if (strcmp(mount_dirs[mount_idx], "/media") == 0) {
// This is /media/[username] - look inside for drives
DIR* user_dir = opendir(mount_path);
if (!user_dir) continue;
struct dirent* user_entry;
while ((user_entry = readdir(user_dir)) != NULL) {
if (user_entry->d_name[0] == '.') continue;
// Check if drive name starts with "OTP"
if (strncmp(user_entry->d_name, "OTP", 3) != 0) continue;
char user_mount_path[2048]; // Increased buffer size
// Verify buffer has enough space before concatenation
size_t mount_len = strlen(mount_path);
size_t entry_len = strlen(user_entry->d_name);
if (mount_len + entry_len + 2 < sizeof(user_mount_path)) {
snprintf(user_mount_path, sizeof(user_mount_path), "%s/%s", mount_path, user_entry->d_name);
// Check if this is a readable directory
DIR* drive_dir = opendir(user_mount_path);
if (drive_dir) {
closedir(drive_dir);
strncpy(otp_drive_path, user_mount_path, path_size - 1);
otp_drive_path[path_size - 1] = '\0';
closedir(user_dir);
closedir(mount_dir);
return 1; // Found OTP drive
}
}
}
closedir(user_dir);
} else if (strcmp(mount_dirs[mount_idx], "/run/media") == 0) {
// For /run/media, we need to go one level deeper (skip username)
DIR* user_dir = opendir(mount_path);
if (!user_dir) continue;
struct dirent* user_entry;
while ((user_entry = readdir(user_dir)) != NULL) {
if (user_entry->d_name[0] == '.') continue;
// Check if drive name starts with "OTP"
if (strncmp(user_entry->d_name, "OTP", 3) != 0) continue;
char user_mount_path[2048]; // Increased buffer size
snprintf(user_mount_path, sizeof(user_mount_path), "%s/%s", mount_path, user_entry->d_name);
// Check if this is a readable directory
DIR* drive_dir = opendir(user_mount_path);
if (drive_dir) {
closedir(drive_dir);
strncpy(otp_drive_path, user_mount_path, path_size - 1);
otp_drive_path[path_size - 1] = '\0';
closedir(user_dir);
closedir(mount_dir);
return 1; // Found OTP drive
}
}
closedir(user_dir);
} else {
// Direct mount point (like /mnt/OTP_DRIVE)
// Check if drive name starts with "OTP"
if (strncmp(mount_entry->d_name, "OTP", 3) == 0) {
DIR* drive_dir = opendir(mount_path);
if (drive_dir) {
closedir(drive_dir);
strncpy(otp_drive_path, mount_path, path_size - 1);
otp_drive_path[path_size - 1] = '\0';
closedir(mount_dir);
return 1; // Found OTP drive
}
}
}
}
closedir(mount_dir);
}
return 0; // No OTP drive found
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// USB DRIVE MANAGEMENT FUNCTIONS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// USB Device Safety Functions
int is_device_removable_usb(const char* device_path) {
char removable_path[512];
FILE* file;
char value;
// Extract device name from path (e.g., /dev/sdb -> sdb)
const char* device_name = strrchr(device_path, '/');
if (!device_name) return 0;
device_name++; // Skip the '/'
// Remove partition number if present (e.g., sdb1 -> sdb)
char base_device[64];
strncpy(base_device, device_name, sizeof(base_device) - 1);
base_device[sizeof(base_device) - 1] = '\0';
// Remove digits at the end to get base device name
int len = strlen(base_device);
while (len > 0 && isdigit(base_device[len - 1])) {
base_device[len - 1] = '\0';
len--;
}
// Check if device is marked as removable
snprintf(removable_path, sizeof(removable_path), "/sys/block/%s/removable", base_device);
file = fopen(removable_path, "r");
if (!file) return 0;
if (fread(&value, 1, 1, file) != 1 || value != '1') {
fclose(file);
return 0;
}
fclose(file);
return 1;
}
int get_device_bus_type(const char* device_path, char* bus_type, size_t bus_type_size) {
char sys_path[512];
char link_target[512];
ssize_t link_len;
// Extract device name from path
const char* device_name = strrchr(device_path, '/');
if (!device_name) return 0;
device_name++; // Skip the '/'
// Remove partition number if present
char base_device[64];
strncpy(base_device, device_name, sizeof(base_device) - 1);
base_device[sizeof(base_device) - 1] = '\0';
int len = strlen(base_device);
while (len > 0 && isdigit(base_device[len - 1])) {
base_device[len - 1] = '\0';
len--;
}
// Read the device symlink to determine bus type
snprintf(sys_path, sizeof(sys_path), "/sys/block/%s/device", base_device);
link_len = readlink(sys_path, link_target, sizeof(link_target) - 1);
if (link_len == -1) return 0;
link_target[link_len] = '\0';
// Check for USB in the path
if (strstr(link_target, "usb")) {
strncpy(bus_type, "usb", bus_type_size - 1);
bus_type[bus_type_size - 1] = '\0';
return 1;
}
// Check for other bus types
if (strstr(link_target, "ata") || strstr(link_target, "scsi")) {
strncpy(bus_type, "ata", bus_type_size - 1);
bus_type[bus_type_size - 1] = '\0';
return 1;
}
strncpy(bus_type, "unknown", bus_type_size - 1);
bus_type[bus_type_size - 1] = '\0';
return 1;
}
int is_system_critical_mount(const char* mount_path) {
// List of critical system mount points that should never be formatted
const char* critical_mounts[] = {
"/", "/boot", "/boot/efi", "/home", "/usr", "/var", "/tmp", "/opt",
"/etc", "/lib", "/lib64", "/bin", "/sbin", "/sys", "/proc", "/dev"
};
const int num_critical_mounts = sizeof(critical_mounts) / sizeof(critical_mounts[0]);
for (int i = 0; i < num_critical_mounts; i++) {
if (strcmp(mount_path, critical_mounts[i]) == 0) {
return 1; // This is a critical system mount
}
}
return 0; // Not a critical system mount
}
int validate_usb_device_safety(const usb_drive_info_t* drive) {
// Check if it's a system critical mount
if (is_system_critical_mount(drive->mount_path)) {
return 0; // Not safe - system critical
}
// Check if device is removable
if (!drive->is_removable) {
return 0; // Not safe - not removable
}
// Check if it's actually a USB device
if (!drive->is_usb_device) {
return 0; // Not safe - not USB
}
// Check if bus type is USB
if (strcmp(drive->bus_type, "usb") != 0) {
return 0; // Not safe - not USB bus
}
// Check drive size limit (1.1 TB = 1.1 * 1024^4 bytes)
const uint64_t max_drive_size = 1100ULL * 1024ULL * 1024ULL * 1024ULL * 1024ULL / 1000ULL; // 1.1 TB
if (drive->total_size > max_drive_size) {
return 0; // Not safe - drive too large (safety limit)
}
return 1; // Safe for USB operations
}
// Generate safety status letters for detailed failure reporting
void get_usb_safety_status(const usb_drive_info_t* drive, char* status, size_t status_size) {
if (!drive || !status || status_size < 6) {
if (status && status_size > 0) {
strncpy(status, "ERROR", status_size - 1);
status[status_size - 1] = '\0';
}
return;
}
// Check if all safety tests pass
if (validate_usb_device_safety(drive)) {
strncpy(status, "SAFE", status_size - 1);
status[status_size - 1] = '\0';
return;
}
// Build failure status with letters
char failures[6] = {0}; // Max 5 failure letters + null terminator
int failure_count = 0;
// C - Critical Mount Check
if (is_system_critical_mount(drive->mount_path)) {
failures[failure_count++] = 'C';
}
// R - Removable Device Check
if (!drive->is_removable) {
failures[failure_count++] = 'R';
}
// U - USB Device Check
if (!drive->is_usb_device) {
failures[failure_count++] = 'U';
}
// B - Bus Type Validation
if (strcmp(drive->bus_type, "usb") != 0) {
failures[failure_count++] = 'B';
}
// S - Size Limit Check
const uint64_t max_drive_size = 1100ULL * 1024ULL * 1024ULL * 1024ULL * 1024ULL / 1000ULL; // 1.1 TB
if (drive->total_size > max_drive_size) {
failures[failure_count++] = 'S';
}
// Copy failure letters to status
if (failure_count > 0) {
strncpy(status, failures, status_size - 1);
status[status_size - 1] = '\0';
} else {
// Shouldn't happen, but fallback
strncpy(status, "UNK", status_size - 1);
status[status_size - 1] = '\0';
}
}
int extract_device_name_from_path(const char* device_path, char* device_name, size_t device_name_size) {
const char* name_start = strrchr(device_path, '/');
if (!name_start) return 0;
name_start++; // Skip the '/'
strncpy(device_name, name_start, device_name_size - 1);
device_name[device_name_size - 1] = '\0';
return 1;
}
// Discover all available USB drives on the system
int discover_usb_drives(usb_drive_info_t** drives, int* drive_count) {
if (!drives || !drive_count) {
return 1; // Error: invalid parameters
}
// Initialize output
*drives = NULL;
*drive_count = 0;
// Allocate array for USB drive information
usb_drive_info_t* drive_array = malloc(MAX_USB_DRIVES * sizeof(usb_drive_info_t));
if (!drive_array) {
return 2; // Error: memory allocation failed
}
int found_count = 0;
const char* mount_dirs[] = {"/media", "/run/media", "/mnt", NULL};
// Search through common mount directories
for (int mount_idx = 0; mount_dirs[mount_idx] != NULL && found_count < MAX_USB_DRIVES; mount_idx++) {
DIR* mount_dir = opendir(mount_dirs[mount_idx]);
if (!mount_dir) continue;
struct dirent* mount_entry;
while ((mount_entry = readdir(mount_dir)) != NULL && found_count < MAX_USB_DRIVES) {
if (mount_entry->d_name[0] == '.') continue;
char mount_path[1024];
snprintf(mount_path, sizeof(mount_path), "%s/%s", mount_dirs[mount_idx], mount_entry->d_name);
// Handle different mount directory structures
if (strcmp(mount_dirs[mount_idx], "/media") == 0) {
// /media/[username] - look inside for drives
DIR* user_dir = opendir(mount_path);
if (!user_dir) continue;
struct dirent* user_entry;
while ((user_entry = readdir(user_dir)) != NULL && found_count < MAX_USB_DRIVES) {
if (user_entry->d_name[0] == '.') continue;
char drive_path[2048];
snprintf(drive_path, sizeof(drive_path), "%s/%s", mount_path, user_entry->d_name);
// Check if this is a readable directory (mounted drive)
DIR* drive_dir = opendir(drive_path);
if (drive_dir) {
closedir(drive_dir);
// Get drive information - include ALL drives for debugging
usb_drive_info_t* drive = &drive_array[found_count];
if (get_usb_drive_info(drive_path, user_entry->d_name, drive) == 0) {
// Store safety validation result in the drive structure
drive->is_safe = validate_usb_device_safety(drive);
found_count++;
}
}
}
closedir(user_dir);
} else if (strcmp(mount_dirs[mount_idx], "/run/media") == 0) {
// /run/media/[username] - look inside for drives
DIR* user_dir = opendir(mount_path);
if (!user_dir) continue;
struct dirent* user_entry;
while ((user_entry = readdir(user_dir)) != NULL && found_count < MAX_USB_DRIVES) {
if (user_entry->d_name[0] == '.') continue;
char drive_path[2048];
snprintf(drive_path, sizeof(drive_path), "%s/%s", mount_path, user_entry->d_name);
DIR* drive_dir = opendir(drive_path);
if (drive_dir) {
closedir(drive_dir);
usb_drive_info_t* drive = &drive_array[found_count];
if (get_usb_drive_info(drive_path, user_entry->d_name, drive) == 0) {
// Store safety validation result in the drive structure
drive->is_safe = validate_usb_device_safety(drive);
found_count++;
}
}
}
closedir(user_dir);
} else {
// Direct mount point (like /mnt/DRIVE_NAME)
DIR* drive_dir = opendir(mount_path);
if (drive_dir) {
closedir(drive_dir);
usb_drive_info_t* drive = &drive_array[found_count];
if (get_usb_drive_info(mount_path, mount_entry->d_name, drive) == 0) {
// Store safety validation result in the drive structure
drive->is_safe = validate_usb_device_safety(drive);
found_count++;
}
}
}
}
closedir(mount_dir);
}
if (found_count == 0) {
free(drive_array);
return 3; // Error: no drives found
}
// Resize array to actual count
usb_drive_info_t* final_array = realloc(drive_array, found_count * sizeof(usb_drive_info_t));
if (!final_array) {
// Keep original array if realloc fails
final_array = drive_array;
}
*drives = final_array;
*drive_count = found_count;
return 0; // Success
}
// Get detailed information about a USB drive
int get_usb_drive_info(const char* mount_path, const char* drive_label, usb_drive_info_t* drive_info) {
if (!mount_path || !drive_label || !drive_info) {
return 1;
}
// Initialize drive info structure
memset(drive_info, 0, sizeof(usb_drive_info_t));
// Set basic information
strncpy(drive_info->mount_path, mount_path, sizeof(drive_info->mount_path) - 1);
strncpy(drive_info->volume_label, drive_label, sizeof(drive_info->volume_label) - 1);
// Check if it's an OTP drive (label starts with "OTP_")
drive_info->is_otp_drive = (strncmp(drive_label, "OTP_", 4) == 0);
// Enhanced device path detection with multiple methods
char device_path[256] = "";
int device_found = 0;
// Method 1: Enhanced /proc/mounts parsing
FILE* mounts = fopen("/proc/mounts", "r");
if (mounts) {
char line[1024];
while (fgets(line, sizeof(line), mounts)) {
char device[256], mountpoint[512], fstype[64];
// Use more robust parsing that handles escaped characters
if (sscanf(line, "%255s %511s %63s", device, mountpoint, fstype) == 3) {
// Handle escaped characters in mount paths (octal sequences like \040 for space)
char decoded_mountpoint[512];
size_t src = 0, dst = 0;
size_t mountpoint_len = strlen(mountpoint);
while (src < mountpoint_len && dst < sizeof(decoded_mountpoint) - 1) {
if (mountpoint[src] == '\\' && src + 3 < mountpoint_len &&
mountpoint[src + 1] >= '0' && mountpoint[src + 1] <= '7') {
// Decode octal escape sequence
int octal_val = (mountpoint[src + 1] - '0') * 64 +
(mountpoint[src + 2] - '0') * 8 +
(mountpoint[src + 3] - '0');
decoded_mountpoint[dst++] = (char)octal_val;
src += 4;
} else {
decoded_mountpoint[dst++] = mountpoint[src++];
}
}
decoded_mountpoint[dst] = '\0';
// Debug: Print comparison details for DUAL DRIVE debugging
if (strstr(mount_path, "DUAL DRIVE") || strstr(decoded_mountpoint, "DUAL DRIVE")) {
printf("DEBUG Method1: mount_path='%s', decoded='%s', device='%s', match=%d\n",
mount_path, decoded_mountpoint, device,
strcmp(decoded_mountpoint, mount_path) == 0);
}
if (strcmp(decoded_mountpoint, mount_path) == 0) {
strncpy(device_path, device, sizeof(device_path) - 1);
device_path[sizeof(device_path) - 1] = '\0';
strncpy(drive_info->filesystem, fstype, sizeof(drive_info->filesystem) - 1);
drive_info->filesystem[sizeof(drive_info->filesystem) - 1] = '\0';
device_found = 1;
break;
}
}
}
fclose(mounts);
}
// Method 2: Try /proc/self/mountinfo for more detailed information
if (!device_found) {
FILE* mountinfo = fopen("/proc/self/mountinfo", "r");
if (mountinfo) {
char line[1024];
while (fgets(line, sizeof(line), mountinfo)) {
// Parse mountinfo format: mountid parentid major:minor root mount_point options - fstype source
char *token = strtok(line, " ");
int field_count = 0;
char *mount_field = NULL, *source_field = NULL;
while (token && field_count < 10) {
field_count++;
if (field_count == 5) { // mount point field
mount_field = token;
} else if (field_count == 9) { // source field (after fstype)
source_field = token;
break;
}
token = strtok(NULL, " ");
}
if (mount_field && source_field) {
// Decode escaped characters in mount point
char decoded_mount[512];
size_t src = 0, dst = 0;
size_t mount_field_len = strlen(mount_field);
while (src < mount_field_len && dst < sizeof(decoded_mount) - 1) {
if (mount_field[src] == '\\' && src + 3 < mount_field_len) {
int octal_val = (mount_field[src + 1] - '0') * 64 +
(mount_field[src + 2] - '0') * 8 +
(mount_field[src + 3] - '0');
decoded_mount[dst++] = (char)octal_val;
src += 4;
} else {
decoded_mount[dst++] = mount_field[src++];
}
}
decoded_mount[dst] = '\0';
// Debug: Print comparison details for DUAL DRIVE debugging
if (strstr(mount_path, "DUAL DRIVE") || strstr(decoded_mount, "DUAL DRIVE")) {
printf("DEBUG Method2: mount_path='%s', decoded='%s', source='%s', match=%d\n",
mount_path, decoded_mount, source_field,
strcmp(decoded_mount, mount_path) == 0);
}
if (strcmp(decoded_mount, mount_path) == 0) {
strncpy(device_path, source_field, sizeof(device_path) - 1);
device_path[sizeof(device_path) - 1] = '\0';
device_found = 1;
break;
}
}
}
fclose(mountinfo);
}
}
// Method 3: Enhanced smart inference from mount path patterns if device detection failed
int inferred_usb = 0;
if (!device_found) {
// Enhanced USB mount path pattern detection
if (strstr(mount_path, "/media/") || strstr(mount_path, "/run/media/")) {
// All /media/ and /run/media/ paths are highly likely to be USB drives
inferred_usb = 1;
strncpy(drive_info->device_name, "inferred_usb", sizeof(drive_info->device_name) - 1);
strncpy(drive_info->bus_type, "usb", sizeof(drive_info->bus_type) - 1);
} else if (strstr(mount_path, "/mnt/") &&
(strstr(mount_path, "USB") || strstr(mount_path, "usb") ||
strstr(mount_path, "DRIVE") || strstr(mount_path, "DISK"))) {
// /mnt/ paths with USB-like naming
inferred_usb = 1;
strncpy(drive_info->device_name, "inferred_usb", sizeof(drive_info->device_name) - 1);
strncpy(drive_info->bus_type, "usb", sizeof(drive_info->bus_type) - 1);
} else {
strncpy(drive_info->device_name, "unknown", sizeof(drive_info->device_name) - 1);
strncpy(drive_info->bus_type, "unknown", sizeof(drive_info->bus_type) - 1);
}
// Set filesystem to unknown if not determined above
if (strlen(drive_info->filesystem) == 0) {
strncpy(drive_info->filesystem, "unknown", sizeof(drive_info->filesystem) - 1);
}
}
// Debug: Print final device detection results for DUAL DRIVE
if (strstr(mount_path, "DUAL DRIVE")) {
printf("DEBUG Final: mount_path='%s', device_found=%d, device_path='%s', inferred_usb=%d\n",
mount_path, device_found, device_path, inferred_usb);
}
// Populate safety fields using the USB safety functions
if (device_found && strlen(device_path) > 0) {
// Extract device name from device path
extract_device_name_from_path(device_path, drive_info->device_name, sizeof(drive_info->device_name));
// Check if device is removable
drive_info->is_removable = is_device_removable_usb(device_path);
// Get bus type
get_device_bus_type(device_path, drive_info->bus_type, sizeof(drive_info->bus_type));
// Check if it's a USB device (based on bus type)
drive_info->is_usb_device = (strcmp(drive_info->bus_type, "usb") == 0);
// Check if it's a system critical mount
drive_info->is_system_mount = is_system_critical_mount(mount_path);
} else if (inferred_usb) {
// Use smart inference for likely USB drives when device detection fails
drive_info->is_removable = 1; // Assume removable for USB mount paths
drive_info->is_usb_device = 1; // Inferred as USB device
drive_info->is_system_mount = is_system_critical_mount(mount_path);
} else {
// Conservative fallback - mark as unknown but not unsafe unless proven otherwise
drive_info->is_removable = 0; // Cannot determine, assume non-removable
drive_info->is_usb_device = 0; // Cannot determine, assume non-USB
drive_info->is_system_mount = is_system_critical_mount(mount_path);
}
// Get filesystem information using statvfs
struct statvfs vfs;
if (statvfs(mount_path, &vfs) == 0) {
drive_info->total_size = (uint64_t)vfs.f_blocks * vfs.f_frsize;
drive_info->available_size = (uint64_t)vfs.f_bavail * vfs.f_frsize;
} else {
// Fallback: try to get basic directory info
struct stat st;
if (stat(mount_path, &st) == 0) {
drive_info->total_size = 0; // Unknown
drive_info->available_size = 0; // Unknown
} else {
return 2; // Cannot access drive
}
}
// Count OTP pad files if it's an OTP drive
drive_info->pad_count = 0;
if (drive_info->is_otp_drive) {
DIR* dir = opendir(mount_path);
if (dir) {
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
if (strstr(entry->d_name, ".pad") && strlen(entry->d_name) == 68) {
drive_info->pad_count++;
}
}
closedir(dir);
}
}
// Set mount status (assume mounted since we can access it)
drive_info->is_mounted = 1;
return 0; // Success
}
// Initialize a USB drive for OTP use
usb_operation_result_t initialize_usb_drive(const char* mount_path, const char* drive_name,
int format_drive, const char* filesystem_type) {
usb_operation_result_t result = {0};
if (!mount_path || !drive_name) {
result.success = 0;
strncpy(result.error_message, "Invalid parameters", sizeof(result.error_message) - 1);
return result;
}
// Validate drive name format
if (strncmp(drive_name, "OTP_", 4) != 0) {
result.success = 0;
strncpy(result.error_message, "Drive name must start with 'OTP_'", sizeof(result.error_message) - 1);
return result;
}
if (strlen(drive_name) > 32 || strlen(drive_name) < 5) {
result.success = 0;
strncpy(result.error_message, "Drive name must be 5-32 characters", sizeof(result.error_message) - 1);
return result;
}
// Check if drive is writable
if (access(mount_path, W_OK) != 0) {
result.success = 0;
strncpy(result.error_message, "Drive is not writable", sizeof(result.error_message) - 1);
return result;
}
// Format drive if requested
if (format_drive) {
printf("Formatting USB drive (this may take several minutes)...\n");
// Get device path from mount path
char device_path[256] = "";
if (get_device_from_mount(mount_path, device_path, sizeof(device_path)) != 0) {
result.success = 0;
strncpy(result.error_message, "Cannot determine device path", sizeof(result.error_message) - 1);
return result;
}
// Unmount before formatting
char umount_cmd[512];
snprintf(umount_cmd, sizeof(umount_cmd), "umount '%s' 2>/dev/null", mount_path);
system(umount_cmd); // Ignore errors
// Format with specified filesystem
char format_cmd[512];
if (strcmp(filesystem_type, "fat32") == 0) {
snprintf(format_cmd, sizeof(format_cmd),
"mkfs.fat -F32 -n '%s' '%s' 2>/dev/null", drive_name, device_path);
} else if (strcmp(filesystem_type, "ext4") == 0) {
snprintf(format_cmd, sizeof(format_cmd),
"mkfs.ext4 -L '%s' '%s' -F 2>/dev/null", drive_name, device_path);
} else {
result.success = 0;
strncpy(result.error_message, "Unsupported filesystem type", sizeof(result.error_message) - 1);
return result;
}
if (system(format_cmd) != 0) {
result.success = 0;
strncpy(result.error_message, "Format operation failed", sizeof(result.error_message) - 1);
return result;
}
// Wait for system to recognize the new filesystem
sleep(2);
// Remount (let system handle this automatically)
printf("Format completed. Please remount the drive if necessary.\n");
}
// Set volume label if not formatting
if (!format_drive) {
if (set_volume_label(mount_path, drive_name) != 0) {
result.success = 0;
strncpy(result.error_message, "Failed to set volume label", sizeof(result.error_message) - 1);
return result;
}
}
// Create OTP directory structure if needed
char otp_readme_path[1024];
snprintf(otp_readme_path, sizeof(otp_readme_path), "%s/OTP_README.txt", mount_path);
FILE* readme = fopen(otp_readme_path, "w");
if (readme) {
fprintf(readme, "OTP Drive: %s\n", drive_name);
time_t init_time = time(NULL);
fprintf(readme, "Initialized: %s", ctime(&init_time));
fprintf(readme, "\nThis USB drive has been initialized for use with the OTP program.\n");
fprintf(readme, "OTP pad files stored on this drive use the same .pad/.state format\n");
fprintf(readme, "as local pads and can be used for encryption/decryption operations.\n");
fprintf(readme, "\nDO NOT modify .pad or .state files manually!\n");
fprintf(readme, "Use the OTP program's pad management features instead.\n");
fclose(readme);
}
result.success = 1;
// Success details are in the error_message field for simplicity
strncpy(result.error_message, "USB drive initialized successfully", sizeof(result.error_message) - 1);
return result;
}
// Set volume label for a USB drive
int set_volume_label(const char* mount_path, const char* new_label) {
if (!mount_path || !new_label) {
return 1;
}
// Get device path
char device_path[256];
if (get_device_from_mount(mount_path, device_path, sizeof(device_path)) != 0) {
return 2;
}
// Try different labeling methods based on filesystem
char label_cmd[512];
// First try FAT32 labeling
snprintf(label_cmd, sizeof(label_cmd), "fatlabel '%s' '%s' 2>/dev/null", device_path, new_label);
if (system(label_cmd) == 0) {
return 0;
}
// Try ext filesystem labeling
snprintf(label_cmd, sizeof(label_cmd), "e2label '%s' '%s' 2>/dev/null", device_path, new_label);
if (system(label_cmd) == 0) {
return 0;
}
// Try generic tune2fs for ext filesystems
snprintf(label_cmd, sizeof(label_cmd), "tune2fs -L '%s' '%s' 2>/dev/null", new_label, device_path);
if (system(label_cmd) == 0) {
return 0;
}
return 3; // All methods failed
}
// Get device path from mount path
int get_device_from_mount(const char* mount_path, char* device_path, size_t device_path_size) {
if (!mount_path || !device_path) {
return 1;
}
FILE* mounts = fopen("/proc/mounts", "r");
if (!mounts) {
return 2;
}
char line[1024];
while (fgets(line, sizeof(line), mounts)) {
char device[256], mountpoint[512];
if (sscanf(line, "%s %s", device, mountpoint) == 2) {
if (strcmp(mountpoint, mount_path) == 0) {
strncpy(device_path, device, device_path_size - 1);
device_path[device_path_size - 1] = '\0';
fclose(mounts);
return 0;
}
}
}
fclose(mounts);
return 3; // Mount point not found
}
// Select USB drive interactively
usb_drive_info_t* select_usb_drive_interactive(const char* title, const char* prompt, int require_otp) {
usb_drive_info_t* drives;
int drive_count;
printf("\n%s\n", title);
printf("Scanning for USB drives...\n");
int result = discover_usb_drives(&drives, &drive_count);
if (result != 0) {
switch (result) {
case 3: // USB_ERROR_NO_DRIVES_FOUND
printf("No USB drives found.\n");
printf("Please ensure your USB drive is properly mounted.\n");
break;
case 2: // USB_ERROR_MEMORY_ALLOCATION
printf("Error: Memory allocation failed.\n");
break;
default:
printf("Error: Failed to discover USB drives (code: %d).\n", result);
break;
}
return NULL;
}
// Count safe drives that meet requirements
int safe_filtered_count = 0;
int total_filtered_count = 0;
for (int i = 0; i < drive_count; i++) {
if (!require_otp || drives[i].is_otp_drive) {
total_filtered_count++;
if (drives[i].is_safe) {
safe_filtered_count++;
}
}
}
if (total_filtered_count == 0) {
if (require_otp) {
printf("No OTP USB drives found.\n");
printf("Initialize a USB drive first using the 'Initialize USB drive' option.\n");
} else {
printf("No USB drives found.\n");
}
free(drives);
return NULL;
}
// Display available drives with safety indicators
printf("\nFound USB drives:\n");
printf("%-3s %-20s %-10s %-12s %-12s %-8s %-4s %-6s\n",
"#", "Label", "Type", "Total", "Free", "FS", "Pads", "Status");
printf("%-3s %-20s %-10s %-12s %-12s %-8s %-4s %-6s\n",
"---", "--------------------", "----------", "----------", "----------", "--------", "----", "------");
int display_index = 1;
int safe_display_index = 1;
for (int i = 0; i < drive_count; i++) {
if (require_otp && !drives[i].is_otp_drive) {
continue; // Skip non-OTP drives when filtering
}
// Format sizes for display
char total_str[16], free_str[16];
format_size_string(drives[i].total_size, total_str, sizeof(total_str));
format_size_string(drives[i].available_size, free_str, sizeof(free_str));
// Display with appropriate formatting
if (drives[i].is_safe) {
// Safe drive - normal display with selectable number
printf("%-3d %-20s %-10s %-12s %-12s %-8s %-4d %-6s\n",
safe_display_index,
drives[i].volume_label,
drives[i].is_otp_drive ? "OTP" : "Standard",
total_str,
free_str,
drives[i].filesystem,
drives[i].pad_count,
"SAFE");
safe_display_index++;
} else {
// Unsafe drive - strikethrough display, no selection number
printf("%-3s \033[9m%-20s %-10s %-12s %-12s %-8s %-4d\033[0m %-6s\n",
"---",
drives[i].volume_label,
drives[i].is_otp_drive ? "OTP" : "Standard",
total_str,
free_str,
drives[i].filesystem,
drives[i].pad_count,
"UNSAFE");
}
display_index++;
}
if (safe_filtered_count == 0) {
printf("\n⚠ WARNING: No safe USB drives found!\n");
printf("All detected drives failed safety validation.\n");
printf("Common issues: System drives, non-removable media, non-USB devices\n");
free(drives);
return NULL;
}
printf("\n%s (1-%d): ", prompt, safe_filtered_count);
char input[10];
if (!fgets(input, sizeof(input), stdin)) {
free(drives);
return NULL;
}
int selection = atoi(input);
if (selection < 1 || selection > safe_filtered_count) {
printf("Invalid selection.\n");
free(drives);
return NULL;
}
// Find the selected safe drive (accounting for filtering and safety)
int current_safe_index = 1;
for (int i = 0; i < drive_count; i++) {
if (require_otp && !drives[i].is_otp_drive) {
continue;
}
if (drives[i].is_safe) {
if (current_safe_index == selection) {
// Create a copy of the selected drive info
usb_drive_info_t* selected_drive = malloc(sizeof(usb_drive_info_t));
if (selected_drive) {
*selected_drive = drives[i];
}
free(drives);
return selected_drive;
}
current_safe_index++;
}
}
free(drives);
return NULL;
}
// Format file size for display
void format_size_string(uint64_t bytes, char* result, size_t result_size) {
if (bytes == 0) {
strncpy(result, "Unknown", result_size - 1);
result[result_size - 1] = '\0';
return;
}
if (bytes < 1024) {
snprintf(result, result_size, "%luB", bytes);
} else if (bytes < 1024 * 1024) {
snprintf(result, result_size, "%.1fKB", (double)bytes / 1024.0);
} else if (bytes < 1024 * 1024 * 1024) {
snprintf(result, result_size, "%.1fMB", (double)bytes / (1024.0 * 1024.0));
} else if (bytes < 1024ULL * 1024 * 1024 * 1024) {
snprintf(result, result_size, "%.2fGB", (double)bytes / (1024.0 * 1024.0 * 1024.0));
} else {
snprintf(result, result_size, "%.2fTB", (double)bytes / (1024.0 * 1024.0 * 1024.0 * 1024.0));
}
}
// Custom base64 encode function
// Copy pad from local storage to USB drive
int copy_pad_to_usb(const char* pad_checksum, const char* usb_mount_path) {
if (!pad_checksum || !usb_mount_path) {
printf("Error: Invalid parameters for pad copy operation\n");
return 1;
}
// Build source paths
char local_pad_path[1024], local_state_path[1024];
get_pad_path(pad_checksum, local_pad_path, local_state_path);
// Check if local pad exists
if (access(local_pad_path, R_OK) != 0) {
printf("Error: Local pad file not found: %s\n", local_pad_path);
return 2;
}
// Build destination paths
char usb_pad_path[1024], usb_state_path[1024];
snprintf(usb_pad_path, sizeof(usb_pad_path), "%s/%s.pad", usb_mount_path, pad_checksum);
snprintf(usb_state_path, sizeof(usb_state_path), "%s/%s.state", usb_mount_path, pad_checksum);
// Check if destination pad already exists
if (access(usb_pad_path, F_OK) == 0) {
printf("Warning: Pad already exists on USB drive: %s\n", pad_checksum);
printf("Overwrite? (y/N): ");
fflush(stdout);
char response[10];
if (fgets(response, sizeof(response), stdin) == NULL ||
(response[0] != 'y' && response[0] != 'Y')) {
printf("Copy operation cancelled.\n");
return 3;
}
}
// Copy pad file
printf("Copying pad file...\n");
if (copy_file(local_pad_path, usb_pad_path) != 0) {
printf("Error: Failed to copy pad file to USB drive\n");
return 4;
}
// Copy state file if it exists
if (access(local_state_path, R_OK) == 0) {
printf("Copying state file...\n");
if (copy_file(local_state_path, usb_state_path) != 0) {
printf("Warning: Failed to copy state file (pad file copied successfully)\n");
// This is not fatal - pad can still be used
}
}
// Verify the copy
printf("Verifying integrity...\n");
if (verify_pad_integrity_cross_drive(local_pad_path, usb_pad_path) != 0) {
printf("Error: Integrity verification failed - removing corrupted copy\n");
unlink(usb_pad_path);
unlink(usb_state_path);
return 5;
}
printf("✓ Pad successfully copied to USB drive: %.16s...\n", pad_checksum);
return 0;
}
// Copy pad from USB drive to local storage
int copy_pad_from_usb(const char* usb_mount_path, const char* pad_checksum) {
if (!usb_mount_path || !pad_checksum) {
printf("Error: Invalid parameters for pad import operation\n");
return 1;
}
// Build source paths on USB
char usb_pad_path[1024], usb_state_path[1024];
snprintf(usb_pad_path, sizeof(usb_pad_path), "%s/%s.pad", usb_mount_path, pad_checksum);
snprintf(usb_state_path, sizeof(usb_state_path), "%s/%s.state", usb_mount_path, pad_checksum);
// Check if USB pad exists
if (access(usb_pad_path, R_OK) != 0) {
printf("Error: Pad file not found on USB drive: %s\n", pad_checksum);
return 2;
}
// Build destination paths
char local_pad_path[1024], local_state_path[1024];
get_pad_path(pad_checksum, local_pad_path, local_state_path);
// Check if local pad already exists
if (access(local_pad_path, F_OK) == 0) {
printf("Warning: Pad already exists locally: %s\n", pad_checksum);
printf("Overwrite? (y/N): ");
fflush(stdout);
char response[10];
if (fgets(response, sizeof(response), stdin) == NULL ||
(response[0] != 'y' && response[0] != 'Y')) {
printf("Import operation cancelled.\n");
return 3;
}
}
// Ensure pads directory exists
if (ensure_pads_directory() != 0) {
printf("Error: Cannot create local pads directory\n");
return 4;
}
// Copy pad file from USB
printf("Importing pad file...\n");
if (copy_file(usb_pad_path, local_pad_path) != 0) {
printf("Error: Failed to import pad file from USB drive\n");
return 5;
}
// Set pad file to read-only
if (chmod(local_pad_path, S_IRUSR) != 0) {
printf("Warning: Cannot set pad file to read-only\n");
}
// Copy state file if it exists
if (access(usb_state_path, R_OK) == 0) {
printf("Importing state file...\n");
if (copy_file(usb_state_path, local_state_path) != 0) {
printf("Warning: Failed to import state file (pad file imported successfully)\n");
// Create a fresh state file with default offset
FILE* new_state = fopen(local_state_path, "wb");
if (new_state) {
uint64_t default_offset = 32; // Reserved bytes
fwrite(&default_offset, sizeof(uint64_t), 1, new_state);
fclose(new_state);
printf("Created new state file with default offset\n");
}
}
} else {
// Create state file if it doesn't exist
printf("Creating state file...\n");
FILE* new_state = fopen(local_state_path, "wb");
if (new_state) {
uint64_t default_offset = 32; // Reserved bytes
fwrite(&default_offset, sizeof(uint64_t), 1, new_state);
fclose(new_state);
}
}
// Verify the import
printf("Verifying integrity...\n");
if (verify_pad_integrity_cross_drive(usb_pad_path, local_pad_path) != 0) {
printf("Error: Integrity verification failed - removing corrupted import\n");
unlink(local_pad_path);
unlink(local_state_path);
return 6;
}
printf("✓ Pad successfully imported from USB drive: %.16s...\n", pad_checksum);
return 0;
}
// Generic file copy function
int copy_file(const char* source_path, const char* dest_path) {
if (!source_path || !dest_path) {
return 1;
}
FILE* source = fopen(source_path, "rb");
if (!source) {
return 2;
}
FILE* dest = fopen(dest_path, "wb");
if (!dest) {
fclose(source);
return 3;
}
// Copy in chunks for large files
unsigned char buffer[64 * 1024]; // 64KB buffer
size_t bytes_read;
int error = 0;
while ((bytes_read = fread(buffer, 1, sizeof(buffer), source)) > 0) {
if (fwrite(buffer, 1, bytes_read, dest) != bytes_read) {
error = 4; // Write error
break;
}
}
fclose(source);
fclose(dest);
if (error) {
unlink(dest_path); // Remove incomplete file
return error;
}
return 0;
}
// List pads available on a USB drive
int list_pads_on_drive(const char* mount_path, char pad_list[][65], int max_pads) {
if (!mount_path || !pad_list || max_pads <= 0) {
return -1;
}
DIR* dir = opendir(mount_path);
if (!dir) {
return -2;
}
int pad_count = 0;
struct dirent* entry;
while ((entry = readdir(dir)) != NULL && pad_count < max_pads) {
// Check if this is a pad file
if (strstr(entry->d_name, ".pad") && strlen(entry->d_name) == 68) {
// Extract checksum (remove .pad extension)
strncpy(pad_list[pad_count], entry->d_name, 64);
pad_list[pad_count][64] = '\0';
pad_count++;
}
}
closedir(dir);
return pad_count;
}
// Verify integrity between two pad files (cross-drive verification)
int verify_pad_integrity_cross_drive(const char* source_pad, const char* dest_pad) {
if (!source_pad || !dest_pad) {
return 1;
}
// Calculate checksums of both files
char source_checksum[65], dest_checksum[65];
if (calculate_checksum(source_pad, source_checksum) != 0) {
printf("Error: Cannot calculate source pad checksum\n");
return 2;
}
if (calculate_checksum(dest_pad, dest_checksum) != 0) {
printf("Error: Cannot calculate destination pad checksum\n");
return 3;
}
// Compare checksums
if (strcmp(source_checksum, dest_checksum) != 0) {
printf("Error: Pad integrity verification failed\n");
printf("Source: %s\n", source_checksum);
printf("Destination: %s\n", dest_checksum);
return 4;
}
// Also verify file sizes match
struct stat source_stat, dest_stat;
if (stat(source_pad, &source_stat) != 0 || stat(dest_pad, &dest_stat) != 0) {
printf("Error: Cannot verify file sizes\n");
return 5;
}
if (source_stat.st_size != dest_stat.st_size) {
printf("Error: File sizes don't match\n");
printf("Source: %lu bytes, Destination: %lu bytes\n",
source_stat.st_size, dest_stat.st_size);
return 6;
}
return 0; // Verification successful
}
// Selective drive duplication with pad selection
int duplicate_drive_selective(const char* source_mount, const char* dest_mount,
char selected_pads[][65], int pad_count) {
if (!source_mount || !dest_mount || !selected_pads || pad_count <= 0) {
printf("Error: Invalid parameters for drive duplication\n");
return 1;
}
printf("Starting selective drive duplication...\n");
printf("Source: %s\n", source_mount);
printf("Destination: %s\n", dest_mount);
printf("Pads to copy: %d\n\n", pad_count);
int success_count = 0;
int error_count = 0;
// Copy each selected pad
for (int i = 0; i < pad_count; i++) {
printf("[%d/%d] Copying pad: %.16s...\n", i + 1, pad_count, selected_pads[i]);
// Build source paths
char source_pad[1024], source_state[1024];
snprintf(source_pad, sizeof(source_pad), "%s/%s.pad", source_mount, selected_pads[i]);
snprintf(source_state, sizeof(source_state), "%s/%s.state", source_mount, selected_pads[i]);
// Build destination paths
char dest_pad[1024], dest_state[1024];
snprintf(dest_pad, sizeof(dest_pad), "%s/%s.pad", dest_mount, selected_pads[i]);
snprintf(dest_state, sizeof(dest_state), "%s/%s.state", dest_mount, selected_pads[i]);
// Check source pad exists
if (access(source_pad, R_OK) != 0) {
printf(" Error: Source pad not found - skipping\n");
error_count++;
continue;
}
// Copy pad file
if (copy_file(source_pad, dest_pad) != 0) {
printf(" Error: Failed to copy pad file - skipping\n");
error_count++;
continue;
}
// Copy state file if it exists
if (access(source_state, R_OK) == 0) {
if (copy_file(source_state, dest_state) != 0) {
printf(" Warning: Failed to copy state file\n");
// Create default state file
FILE* new_state = fopen(dest_state, "wb");
if (new_state) {
uint64_t default_offset = 32;
fwrite(&default_offset, sizeof(uint64_t), 1, new_state);
fclose(new_state);
}
}
} else {
// Create default state file
FILE* new_state = fopen(dest_state, "wb");
if (new_state) {
uint64_t default_offset = 32;
fwrite(&default_offset, sizeof(uint64_t), 1, new_state);
fclose(new_state);
}
}
// Verify integrity
if (verify_pad_integrity_cross_drive(source_pad, dest_pad) != 0) {
printf(" Error: Integrity verification failed - removing copy\n");
unlink(dest_pad);
unlink(dest_state);
error_count++;
continue;
}
printf(" ✓ Success\n");
success_count++;
}
printf("\nDuplication completed:\n");
printf("Successfully copied: %d pads\n", success_count);
printf("Errors encountered: %d pads\n", error_count);
return (error_count > 0) ? error_count : 0;
}
// Verify complete drive duplication
int verify_drive_duplication(const char* source_mount, const char* dest_mount) {
if (!source_mount || !dest_mount) {
return 1;
}
printf("Verifying drive duplication...\n");
// Get list of pads on source drive
char source_pads[100][65];
int source_count = list_pads_on_drive(source_mount, source_pads, 100);
if (source_count < 0) {
printf("Error: Cannot list pads on source drive\n");
return 2;
}
// Get list of pads on destination drive
char dest_pads[100][65];
int dest_count = list_pads_on_drive(dest_mount, dest_pads, 100);
if (dest_count < 0) {
printf("Error: Cannot list pads on destination drive\n");
return 3;
}
printf("Source drive: %d pads\n", source_count);
printf("Destination drive: %d pads\n", dest_count);
int verified_count = 0;
int error_count = 0;
// Verify each pad on source exists on destination and matches
for (int i = 0; i < source_count; i++) {
printf("Verifying pad: %.16s...\n", source_pads[i]);
// Check if this pad exists on destination
int found = 0;
for (int j = 0; j < dest_count; j++) {
if (strcmp(source_pads[i], dest_pads[j]) == 0) {
found = 1;
break;
}
}
if (!found) {
printf(" Error: Pad missing on destination drive\n");
error_count++;
continue;
}
// Verify integrity
char source_path[1024], dest_path[1024];
snprintf(source_path, sizeof(source_path), "%s/%s.pad", source_mount, source_pads[i]);
snprintf(dest_path, sizeof(dest_path), "%s/%s.pad", dest_mount, source_pads[i]);
if (verify_pad_integrity_cross_drive(source_path, dest_path) != 0) {
printf(" Error: Integrity verification failed\n");
error_count++;
continue;
}
printf(" ✓ Verified\n");
verified_count++;
}
printf("\nVerification completed:\n");
printf("Successfully verified: %d pads\n", verified_count);
printf("Errors found: %d pads\n", error_count);
return (error_count > 0) ? error_count : 0;
}
char* custom_base64_encode(const unsigned char* input, int length) {
int output_length = 4 * ((length + 2) / 3);
char* encoded = malloc(output_length + 1);
if (!encoded) return NULL;
int i, j;
for (i = 0, j = 0; i < length;) {
uint32_t octet_a = i < length ? input[i++] : 0;
uint32_t octet_b = i < length ? input[i++] : 0;
uint32_t octet_c = i < length ? input[i++] : 0;
uint32_t triple = (octet_a << 16) + (octet_b << 8) + octet_c;
encoded[j++] = base64_chars[(triple >> 18) & 63];
encoded[j++] = base64_chars[(triple >> 12) & 63];
encoded[j++] = base64_chars[(triple >> 6) & 63];
encoded[j++] = base64_chars[triple & 63];
}
// Add padding
for (int pad = 0; pad < (3 - length % 3) % 3; pad++) {
encoded[output_length - 1 - pad] = '=';
}
encoded[output_length] = '\0';
return encoded;
}
// Custom base64 decode function
unsigned char* custom_base64_decode(const char* input, int* output_length) {
int input_length = strlen(input);
if (input_length % 4 != 0) return NULL;
*output_length = input_length / 4 * 3;
if (input[input_length - 1] == '=') (*output_length)--;
if (input[input_length - 2] == '=') (*output_length)--;
unsigned char* decoded = malloc(*output_length);
if (!decoded) return NULL;
int i, j;
for (i = 0, j = 0; i < input_length;) {
int sextet_a = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]];
int sextet_b = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]];
int sextet_c = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]];
int sextet_d = input[i] == '=' ? 0 & i++ : base64_decode_table[(unsigned char)input[i++]];
if (sextet_a == -1 || sextet_b == -1 || sextet_c == -1 || sextet_d == -1) {
free(decoded);
return NULL;
}
uint32_t triple = (sextet_a << 18) + (sextet_b << 12) + (sextet_c << 6) + sextet_d;
if (j < *output_length) decoded[j++] = (triple >> 16) & 255;
if (j < *output_length) decoded[j++] = (triple >> 8) & 255;
if (j < *output_length) decoded[j++] = triple & 255;
}
return decoded;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// ADD ENTROPY
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// TRUERNG DEVICE DETECTION AND COMMUNICATION
// Ported from true_rng/main.c for entropy collection
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Read USB device info from sysfs (ported from TrueRNG reference)
int read_usb_device_info(const char* port_name, char* vid, char* pid) {
char path[512];
FILE *fp;
// Try to read idVendor first (works for both ttyUSB and ttyACM devices)
snprintf(path, sizeof(path), "/sys/class/tty/%s/device/../idVendor", port_name);
fp = fopen(path, "r");
if (fp) {
if (fgets(vid, 8, fp) != NULL) {
// Remove newline if present
int len = strlen(vid);
if (len > 0 && vid[len-1] == '\n') {
vid[len-1] = '\0';
}
} else {
fclose(fp);
return 0;
}
fclose(fp);
} else {
return 0;
}
// Try to read idProduct
snprintf(path, sizeof(path), "/sys/class/tty/%s/device/../idProduct", port_name);
fp = fopen(path, "r");
if (fp) {
if (fgets(pid, 8, fp) != NULL) {
// Remove newline if present
int len = strlen(pid);
if (len > 0 && pid[len-1] == '\n') {
pid[len-1] = '\0';
}
} else {
fclose(fp);
return 0;
}
fclose(fp);
return 1;
} else {
return 0;
}
}
// Find TrueRNG device port (ported and adapted from TrueRNG reference)
// Returns: 0=not found, 1=TrueRNGproV2, 2=TrueRNGpro, 3=TrueRNG
int find_truerng_port(char* port_path, size_t port_path_size, truerng_device_type_t* device_type) {
DIR *dir;
struct dirent *entry;
char vid[8], pid[8];
int device_found = 0;
dir = opendir("/dev");
if (dir == NULL) {
return 0;
}
while ((entry = readdir(dir)) != NULL) {
// Look for ttyUSB* or ttyACM* devices
if (strncmp(entry->d_name, "ttyUSB", 6) == 0 ||
strncmp(entry->d_name, "ttyACM", 6) == 0) {
if (read_usb_device_info(entry->d_name, vid, pid)) {
// Convert to uppercase for comparison
for (int i = 0; vid[i]; i++) vid[i] = toupper(vid[i]);
for (int i = 0; pid[i]; i++) pid[i] = toupper(pid[i]);
// Check for TrueRNGproV2
if (strcmp(vid, TRUERNGPROV2_VID) == 0 && strcmp(pid, TRUERNGPROV2_PID) == 0) {
snprintf(port_path, port_path_size, "/dev/%s", entry->d_name);
*device_type = TRUERNG_PRO_V2;
device_found = 1;
break;
}
// Check for TrueRNGpro
if (strcmp(vid, TRUERNGPRO_VID) == 0 && strcmp(pid, TRUERNGPRO_PID) == 0) {
snprintf(port_path, port_path_size, "/dev/%s", entry->d_name);
*device_type = TRUERNG_PRO;
device_found = 2;
break;
}
// Check for TrueRNG
if (strcmp(vid, TRUERNG_VID) == 0 && strcmp(pid, TRUERNG_PID) == 0) {
snprintf(port_path, port_path_size, "/dev/%s", entry->d_name);
*device_type = TRUERNG_ORIGINAL;
device_found = 3;
break;
}
}
}
}
closedir(dir);
return device_found;
}
// Setup serial port for TrueRNG communication (ported from TrueRNG reference)
int setup_truerng_serial_port(const char* port_path) {
int fd;
struct termios tty;
fd = open(port_path, O_RDWR | O_NOCTTY);
if (fd < 0) {
return -1;
}
// Get current port settings
if (tcgetattr(fd, &tty) != 0) {
close(fd);
return -1;
}
// Set baud rate (TrueRNG devices use 9600)
cfsetospeed(&tty, B9600);
cfsetispeed(&tty, B9600);
// 8N1 mode
tty.c_cflag &= ~PARENB; // No parity
tty.c_cflag &= ~CSTOPB; // 1 stop bit
tty.c_cflag &= ~CSIZE; // Clear size bits
tty.c_cflag |= CS8; // 8 data bits
tty.c_cflag &= ~CRTSCTS; // No hardware flow control
tty.c_cflag |= CREAD | CLOCAL; // Enable reading and ignore modem controls
// Raw input mode
tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
tty.c_iflag &= ~(IXON | IXOFF | IXANY);
tty.c_oflag &= ~OPOST;
// Set for blocking reads - wait for data indefinitely
tty.c_cc[VMIN] = 1; // Block until at least 1 character is received
tty.c_cc[VTIME] = 0; // No timeout
// Apply settings
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
close(fd);
return -1;
}
// Flush input buffer
tcflush(fd, TCIFLUSH);
// Set DTR
int status;
ioctl(fd, TIOCMGET, &status);
status |= TIOCM_DTR;
ioctl(fd, TIOCMSET, &status);
return fd;
}
// Get friendly name for TrueRNG device type
const char* get_truerng_device_name(truerng_device_type_t device_type) {
switch (device_type) {
case TRUERNG_PRO_V2: return "TrueRNGproV2";
case TRUERNG_PRO: return "TrueRNGpro";
case TRUERNG_ORIGINAL: return "TrueRNG";
default: return "Unknown";
}
}
// Collect entropy from TrueRNG device with equivalent quality to keyboard entropy
int collect_truerng_entropy(unsigned char* entropy_buffer, size_t target_bytes,
size_t* collected_bytes, int display_progress) {
char port_path[512];
truerng_device_type_t device_type;
int serial_fd = -1;
// Find TrueRNG device
if (!find_truerng_port(port_path, sizeof(port_path), &device_type)) {
if (display_progress) {
printf("No TrueRNG device found.\n");
printf("\nSupported devices:\n");
printf(" - TrueRNG (PID: %s, VID: %s)\n", TRUERNG_VID, TRUERNG_PID);
printf(" - TrueRNGpro (PID: %s, VID: %s)\n", TRUERNGPRO_VID, TRUERNGPRO_PID);
printf(" - TrueRNGproV2 (PID: %s, VID: %s)\n", TRUERNGPROV2_VID, TRUERNGPROV2_PID);
printf("\nPlease connect a TrueRNG device and try again.\n");
}
return 1; // Device not found
}
if (display_progress) {
printf("Found %s at %s\n", get_truerng_device_name(device_type), port_path);
printf("Collecting %zu bytes of entropy...\n", target_bytes);
}
// Setup serial port
serial_fd = setup_truerng_serial_port(port_path);
if (serial_fd < 0) {
if (display_progress) {
printf("Error: Cannot open TrueRNG device at %s\n", port_path);
printf("Check device permissions or run as root.\n");
}
return 2; // Serial port setup failed
}
// Collect entropy data
size_t bytes_read = 0;
unsigned char buffer[1024]; // Read in 1KB chunks
time_t start_time = time(NULL);
while (bytes_read < target_bytes) {
size_t chunk_size = sizeof(buffer);
if (target_bytes - bytes_read < chunk_size) {
chunk_size = target_bytes - bytes_read;
}
size_t bytes_in_chunk = 0;
while (bytes_in_chunk < chunk_size) {
ssize_t result = read(serial_fd, buffer + bytes_in_chunk, chunk_size - bytes_in_chunk);
if (result < 0) {
if (display_progress) {
printf("Error: Failed to read from TrueRNG device\n");
}
close(serial_fd);
return 3; // Read failed
} else if (result == 0) {
if (display_progress) {
printf("Error: No data received from TrueRNG device\n");
}
close(serial_fd);
return 4; // No data
}
bytes_in_chunk += result;
}
// Copy to entropy buffer
memcpy(entropy_buffer + bytes_read, buffer, chunk_size);
bytes_read += chunk_size;
// Show progress for large collections
if (display_progress && bytes_read % (4 * 1024) == 0) { // Every 4KB
double percentage = (double)bytes_read / target_bytes * 100.0;
printf("Progress: %.1f%% (%zu/%zu bytes)\r", percentage, bytes_read, target_bytes);
fflush(stdout);
}
}
close(serial_fd);
if (display_progress) {
double collection_time = difftime(time(NULL), start_time);
printf("\n✓ TrueRNG entropy collection complete!\n");
printf(" Collected: %zu bytes in %.1f seconds\n", bytes_read, collection_time);
printf(" Device: %s\n", get_truerng_device_name(device_type));
if (collection_time > 0) {
printf(" Rate: %.1f KB/s\n", (double)bytes_read / collection_time / 1024.0);
}
}
*collected_bytes = bytes_read;
return 0; // Success
}
// Collect dice entropy with manual input validation
int collect_dice_entropy(unsigned char* entropy_buffer, size_t target_bytes,
size_t* collected_bytes, int display_progress) {
if (display_progress) {
printf("=== Dice Entropy Collection ===\n");
printf("Enter dice rolls as sequences of digits 1-6.\n");
printf("Target: %zu bytes (%zu dice rolls needed)\n", target_bytes, target_bytes * 4);
printf("Press Enter after each sequence, or 'done' when finished.\n\n");
}
size_t entropy_bits = 0;
size_t target_bits = target_bytes * 8;
unsigned char current_byte = 0;
int bits_in_byte = 0;
size_t bytes_written = 0;
char input[256];
while (entropy_bits < target_bits && bytes_written < target_bytes) {
if (display_progress) {
double percentage = (double)entropy_bits / target_bits * 100.0;
printf("Progress: %.1f%% (%zu/%zu bits) - Enter dice rolls: ",
percentage, entropy_bits, target_bits);
fflush(stdout);
}
if (!fgets(input, sizeof(input), stdin)) {
if (display_progress) {
printf("Error: Failed to read input\n");
}
return 1;
}
// Remove newline
input[strcspn(input, "\n")] = 0;
// Check for done command
if (strcmp(input, "done") == 0 && entropy_bits >= target_bits / 2) {
break; // Allow early exit if we have at least half the target
}
// Process dice rolls
for (size_t i = 0; input[i] && entropy_bits < target_bits && bytes_written < target_bytes; i++) {
char c = input[i];
if (c >= '1' && c <= '6') {
// Convert dice roll (1-6) to 3 bits of entropy
unsigned char roll_value = c - '1'; // 0-5
// Pack 3 bits into current byte
for (int bit = 2; bit >= 0 && entropy_bits < target_bits; bit--) {
current_byte = (current_byte << 1) | ((roll_value >> bit) & 1);
bits_in_byte++;
entropy_bits++;
if (bits_in_byte == 8) {
entropy_buffer[bytes_written++] = current_byte;
current_byte = 0;
bits_in_byte = 0;
}
}
}
}
}
// Handle partial byte
if (bits_in_byte > 0 && bytes_written < target_bytes) {
// Pad remaining bits with zeros
current_byte <<= (8 - bits_in_byte);
entropy_buffer[bytes_written++] = current_byte;
}
if (display_progress) {
printf("\n✓ Dice entropy collection complete!\n");
printf(" Collected: %zu bytes from dice rolls\n", bytes_written);
printf(" Entropy bits: %zu\n", entropy_bits);
}
*collected_bytes = bytes_written;
return 0; // Success
}
// Collect entropy by source type with unified interface
int collect_entropy_by_source(entropy_source_t source, unsigned char* entropy_buffer,
size_t target_bytes, size_t* collected_bytes, int display_progress) {
switch (source) {
case ENTROPY_SOURCE_KEYBOARD:
return collect_entropy_with_feedback(entropy_buffer, target_bytes, collected_bytes, 1);
case ENTROPY_SOURCE_TRUERNG:
return collect_truerng_entropy(entropy_buffer, target_bytes, collected_bytes, display_progress);
case ENTROPY_SOURCE_DICE:
return collect_dice_entropy(entropy_buffer, target_bytes, collected_bytes, display_progress);
default:
if (display_progress) {
printf("Error: Unknown entropy source\n");
}
return 1;
}
}
double get_precise_time(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec + ts.tv_nsec / 1000000000.0;
}
void draw_progress_bar(double percentage, int width) {
int filled = (int)(percentage / 100.0 * width);
if (filled > width) filled = width;
printf("[");
for (int i = 0; i < filled; i++) {
printf("");
}
for (int i = filled; i < width; i++) {
printf("");
}
printf("]");
}
void draw_quality_bar(double quality, int width, const char* label) {
int filled = (int)(quality / 100.0 * width);
if (filled > width) filled = width;
// Color coding based on quality
const char* color;
if (quality >= 80) color = "\033[32m"; // Green
else if (quality >= 60) color = "\033[33m"; // Yellow
else color = "\033[31m"; // Red
printf("%s", color);
draw_progress_bar(quality, width);
printf("\033[0m %-10s", label); // Reset color
}
double calculate_timing_quality(const entropy_collection_state_t* state) {
// Analyze timing variance between keypresses
if (state->collected_bytes < 32) return 0.0; // Need minimum data
// Simplified timing quality based on collection rate and variation
double elapsed = get_precise_time() - state->collection_start_time;
if (elapsed < 0.1) return 0.0;
double rate = state->collected_bytes / elapsed;
// Optimal rate is around 50-200 bytes/second (moderate typing with good timing variance)
if (rate >= 50 && rate <= 200) return 90.0;
if (rate >= 20 && rate <= 500) return 70.0;
if (rate >= 10 && rate <= 1000) return 50.0;
return 30.0;
}
double calculate_variety_quality(const entropy_collection_state_t* state) {
// Analyze key variety and distribution
if (state->collected_bytes < 16) return 0.0;
// Calculate entropy from key histogram
double entropy = 0.0;
size_t total_keys = 0;
// Count total keypresses
for (int i = 0; i < 256; i++) {
total_keys += state->key_histogram[i];
}
if (total_keys == 0) return 0.0;
// Calculate Shannon entropy
for (int i = 0; i < 256; i++) {
if (state->key_histogram[i] > 0) {
double p = (double)state->key_histogram[i] / total_keys;
entropy -= p * log2(p);
}
}
// Convert entropy to quality score (0-100)
double max_entropy = log2(256); // Perfect entropy for 8-bit keyspace
double normalized_entropy = entropy / max_entropy;
// Scale based on unique keys as well
double unique_key_factor = (double)state->unique_keys / 50.0; // 50+ unique keys is excellent
if (unique_key_factor > 1.0) unique_key_factor = 1.0;
return (normalized_entropy * 70.0 + unique_key_factor * 30.0);
}
unsigned char calculate_overall_quality(const entropy_collection_state_t* state) {
double timing = calculate_timing_quality(state);
double variety = calculate_variety_quality(state);
// Simple collection progress bonus
double progress_bonus = (double)state->collected_bytes / state->target_bytes * 20.0;
if (progress_bonus > 20.0) progress_bonus = 20.0;
// Weighted average
double overall = (timing * 0.4 + variety * 0.4 + progress_bonus);
if (overall > 100.0) overall = 100.0;
return (unsigned char)overall;
}
void display_entropy_progress(const entropy_collection_state_t* state) {
// Calculate percentages
double progress = (double)state->collected_bytes / state->target_bytes * 100.0;
if (progress > 100.0) progress = 100.0;
double quality = state->quality_score;
double timing_quality = calculate_timing_quality(state);
double variety_quality = calculate_variety_quality(state);
// Clear previous output and redraw
printf("\033[2K\r"); // Clear line
printf("\033[A\033[2K\r"); // Move up and clear
printf("\033[A\033[2K\r"); // Move up and clear
printf("\033[A\033[2K\r"); // Move up and clear
printf("\033[A\033[2K\r"); // Move up and clear
printf("\033[A\033[2K\r"); // Move up and clear
// Header
printf("Adding Entropy to Pad - Target: %zu bytes\n\n", state->target_bytes);
// Main progress bar
printf("Progress: ");
draw_progress_bar(progress, 50);
printf(" %.1f%% (%zu/%zu bytes)\n", progress, state->collected_bytes, state->target_bytes);
// Quality indicators
printf("Quality: ");
draw_quality_bar(quality, 50, "OVERALL");
printf("\n");
printf("Timing: ");
draw_quality_bar(timing_quality, 50, "VARIED");
printf("\n");
printf("Keys: ");
draw_quality_bar(variety_quality, 50, "DIVERSE");
printf("\n");
// Instructions
if (state->collected_bytes >= 1024 && state->auto_complete_enabled) {
printf("\nPress ESC to finish (minimum reached) or continue typing...");
} else if (state->collected_bytes < 1024) {
printf("\nType random keys... (%zu more bytes needed)", 1024 - state->collected_bytes);
} else {
printf("\nType random keys or press ESC when satisfied...");
}
fflush(stdout);
}
// Chacha20 key derivation from collected entropy
int derive_chacha20_params(const unsigned char* entropy_data, size_t entropy_size,
unsigned char key[32], unsigned char nonce[12]) {
if (!entropy_data || entropy_size < 512 || !key || !nonce) {
return 1; // Error: insufficient entropy or null pointers
}
// Phase 1: Generate base key from entropy using enhanced XOR checksum method
unsigned char enhanced_checksum[44]; // 32 key + 12 nonce
memset(enhanced_checksum, 0, 44);
// Mix entropy data similar to calculate_checksum but for 44 bytes
for (size_t i = 0; i < entropy_size; i++) {
unsigned char bucket = i % 44;
enhanced_checksum[bucket] ^= entropy_data[i] ^
((i >> 8) & 0xFF) ^
((i >> 16) & 0xFF) ^
((i >> 24) & 0xFF);
}
// Phase 2: Add system entropy for additional randomness
unsigned char system_entropy[32];
FILE* urandom = fopen("/dev/urandom", "rb");
if (!urandom) {
return 2; // Error: cannot access system entropy
}
if (fread(system_entropy, 1, 32, urandom) != 32) {
fclose(urandom);
return 2; // Error: insufficient system entropy
}
fclose(urandom);
// Mix system entropy into derived key
for (int i = 0; i < 32; i++) {
enhanced_checksum[i] ^= system_entropy[i];
}
// Extract key and nonce
memcpy(key, enhanced_checksum, 32);
memcpy(nonce, enhanced_checksum + 32, 12);
return 0; // Success
}
// Check if a pad is unused (0% usage)
int is_pad_unused(const char* pad_chksum) {
uint64_t used_bytes;
if (read_state_offset(pad_chksum, &used_bytes) != 0) {
return 0; // Error reading state, assume used
}
return (used_bytes <= 32); // Only reserved bytes used (32 bytes for checksum encryption)
}
// Safely rename pad files (pad and state) from old to new checksum
int rename_pad_files_safely(const char* old_chksum, const char* new_chksum) {
char old_pad_path[1024], new_pad_path[1024];
char old_state_path[1024], new_state_path[1024];
// Construct file paths
snprintf(old_pad_path, sizeof(old_pad_path), "%s/%s.pad", current_pads_dir, old_chksum);
snprintf(new_pad_path, sizeof(new_pad_path), "%s/%s.pad", current_pads_dir, new_chksum);
snprintf(old_state_path, sizeof(old_state_path), "%s/%s.state", current_pads_dir, old_chksum);
snprintf(new_state_path, sizeof(new_state_path), "%s/%s.state", current_pads_dir, new_chksum);
// Check if new files would conflict with existing files
if (access(new_pad_path, F_OK) == 0) {
printf("Error: New pad file already exists: %s\n", new_pad_path);
return 1; // Conflict
}
// Rename pad file
if (rename(old_pad_path, new_pad_path) != 0) {
printf("Error: Failed to rename pad file from %s to %s\n", old_pad_path, new_pad_path);
return 2; // Pad rename failed
}
// Rename state file (if it exists)
if (access(old_state_path, F_OK) == 0) {
if (rename(old_state_path, new_state_path) != 0) {
printf("Warning: Failed to rename state file, but pad file was renamed successfully\n");
// Try to rollback pad file rename
rename(new_pad_path, old_pad_path);
return 3; // State rename failed
}
}
return 0; // Success
}
// Update pad checksum after entropy addition
int update_pad_checksum_after_entropy(const char* old_chksum, char* new_chksum) {
char pad_path[1024];
snprintf(pad_path, sizeof(pad_path), "%s/%s.pad", current_pads_dir, old_chksum);
// Calculate new checksum of the modified pad
if (calculate_checksum(pad_path, new_chksum) != 0) {
printf("Error: Cannot calculate new pad checksum\n");
return 1;
}
// Check if checksum actually changed
if (strcmp(old_chksum, new_chksum) == 0) {
printf("Warning: Pad checksum unchanged after entropy addition\n");
return 2; // Checksum didn't change (unusual but not fatal)
}
// Rename pad files to use new checksum
if (rename_pad_files_safely(old_chksum, new_chksum) != 0) {
return 3; // Rename failed
}
// Update default pad preference if this was the default pad
char* current_default = get_default_pad_path();
if (current_default) {
// Check if the old pad was the default
if (strstr(current_default, old_chksum)) {
// Update to new checksum
char new_default_path[1024];
snprintf(new_default_path, sizeof(new_default_path), "%s/%s.pad", current_pads_dir, new_chksum);
if (set_default_pad_path(new_default_path) != 0) {
printf("Warning: Failed to update default pad preference\n");
} else {
printf("Updated default pad to new checksum: %.16s...\n", new_chksum);
}
}
free(current_default);
}
return 0; // Success
}
// Enhanced entropy collection with visual feedback
int collect_entropy_with_feedback(unsigned char* entropy_buffer, size_t target_bytes,
size_t* collected_bytes, int allow_early_exit) {
struct termios original_termios;
entropy_collection_state_t state = {0};
// Initialize state
state.target_bytes = target_bytes;
state.auto_complete_enabled = allow_early_exit;
state.collection_start_time = get_precise_time();
// Setup raw terminal
if (setup_raw_terminal(&original_termios) != 0) {
printf("Error: Cannot setup terminal for entropy collection\n");
return 1;
}
// Clear screen area for display
printf("\n\n\n\n\n\n");
unsigned char entropy_block[16];
struct timespec timestamp;
uint32_t sequence_counter = 0;
char key;
unsigned char seen_keys[256] = {0};
*collected_bytes = 0;
while (state.collected_bytes < target_bytes) {
// Update display
state.quality_score = calculate_overall_quality(&state);
display_entropy_progress(&state);
// Non-blocking read
if (read(STDIN_FILENO, &key, 1) == 1) {
// Handle ESC key for early exit
if (key == 27 && allow_early_exit && state.collected_bytes >= 1024) {
break; // Early exit allowed
}
// Record keypress timing
double current_time = get_precise_time();
state.last_keypress_time = current_time;
// Update key histogram
state.key_histogram[(unsigned char)key]++;
// Get high precision timestamp
clock_gettime(CLOCK_MONOTONIC, &timestamp);
// Create enhanced entropy block: [key][timestamp][sequence][quality_bits]
entropy_block[0] = key;
memcpy(&entropy_block[1], &timestamp.tv_sec, 8);
memcpy(&entropy_block[9], &timestamp.tv_nsec, 4);
memcpy(&entropy_block[13], &sequence_counter, 2);
entropy_block[15] = (unsigned char)(current_time * 1000) & 0xFF; // Sub-millisecond timing
// Add to entropy buffer
if (state.collected_bytes + 16 <= MAX_ENTROPY_BUFFER) {
memcpy(entropy_buffer + state.collected_bytes, entropy_block, 16);
state.collected_bytes += 16;
}
sequence_counter++;
// Track unique keys
if (!seen_keys[(unsigned char)key]) {
seen_keys[(unsigned char)key] = 1;
state.unique_keys++;
}
} else {
// No key available, just sleep and wait for keystrokes
usleep(10000); // 10ms delay - wait for keystrokes, don't add timing entropy
}
// Auto-complete at target if enabled
if (state.collected_bytes >= target_bytes) {
break;
}
}
// Final display update
state.quality_score = calculate_overall_quality(&state);
display_entropy_progress(&state);
// Summary
double collection_time = get_precise_time() - state.collection_start_time;
printf("\n\n✓ Entropy collection complete!\n");
printf(" Collected: %zu bytes in %.1f seconds\n", state.collected_bytes, collection_time);
printf(" Quality: %d%% (Excellent: 80%%+, Good: 60%%+)\n", state.quality_score);
printf(" Unique keys: %zu\n", state.unique_keys);
// Restore terminal
restore_terminal(&original_termios);
*collected_bytes = state.collected_bytes;
return 0;
}
// Keyboard entropy functions
int setup_raw_terminal(struct termios* original_termios) {
struct termios new_termios;
if (tcgetattr(STDIN_FILENO, original_termios) != 0) {
return 1;
}
new_termios = *original_termios;
new_termios.c_lflag &= ~(ICANON | ECHO);
new_termios.c_cc[VMIN] = 0;
new_termios.c_cc[VTIME] = 0;
if (tcsetattr(STDIN_FILENO, TCSANOW, &new_termios) != 0) {
return 1;
}
// Set stdin to non-blocking
int flags = fcntl(STDIN_FILENO, F_GETFL);
if (fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK) == -1) {
tcsetattr(STDIN_FILENO, TCSANOW, original_termios);
return 1;
}
return 0;
}
void restore_terminal(struct termios* original_termios) {
tcsetattr(STDIN_FILENO, TCSANOW, original_termios);
// Reset stdin to blocking
int flags = fcntl(STDIN_FILENO, F_GETFL);
fcntl(STDIN_FILENO, F_SETFL, flags & ~O_NONBLOCK);
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// UNIVERSAL CORE FUNCTIONS FOR CODE CONSOLIDATION
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Universal XOR operation - handles both encryption and decryption
// Since XOR is symmetric, this single function replaces all 6 duplicate XOR loops
int universal_xor_operation(const unsigned char* data, size_t data_len,
const unsigned char* pad_data, unsigned char* result) {
if (!data || !pad_data || !result) {
return 1; // Error: null pointer
}
for (size_t i = 0; i < data_len; i++) {
result[i] = data[i] ^ pad_data[i];
}
return 0; // Success
}
// Universal ASCII message parser - consolidates 4 duplicate parsing implementations
// Extracts checksum, offset, and base64 data from ASCII armored messages
int parse_ascii_message(const char* message, char* chksum, uint64_t* offset, char* base64_data) {
if (!message || !chksum || !offset || !base64_data) {
return 1; // Error: null pointer
}
char *message_copy = strdup(message);
if (!message_copy) {
return 1; // Memory allocation failed
}
char *line_ptr = strtok(message_copy, "\n");
int found_begin = 0;
int in_data_section = 0;
int found_chksum = 0, found_offset = 0;
// Initialize output
chksum[0] = '\0';
*offset = 0;
base64_data[0] = '\0';
while (line_ptr != NULL) {
if (strcmp(line_ptr, "-----BEGIN OTP MESSAGE-----") == 0) {
found_begin = 1;
}
else if (strcmp(line_ptr, "-----END OTP MESSAGE-----") == 0) {
break;
}
else if (found_begin) {
if (strncmp(line_ptr, "Pad-ChkSum: ", 12) == 0) {
strncpy(chksum, line_ptr + 12, 64);
chksum[64] = '\0';
found_chksum = 1;
}
else if (strncmp(line_ptr, "Pad-Offset: ", 12) == 0) {
*offset = strtoull(line_ptr + 12, NULL, 10);
found_offset = 1;
}
else if (strlen(line_ptr) == 0) {
in_data_section = 1;
}
else if (in_data_section) {
strncat(base64_data, line_ptr, MAX_INPUT_SIZE * 2 - strlen(base64_data) - 1);
}
else if (strncmp(line_ptr, "Version:", 8) != 0 && strncmp(line_ptr, "Pad-", 4) != 0) {
// This might be base64 data without a blank line separator
strncat(base64_data, line_ptr, MAX_INPUT_SIZE * 2 - strlen(base64_data) - 1);
}
}
line_ptr = strtok(NULL, "\n");
}
free(message_copy);
if (!found_begin || !found_chksum || !found_offset) {
return 2; // Error: incomplete message format
}
return 0; // Success
}
// Universal pad data loader - consolidates pad loading and validation logic
// Loads pad data at specified offset and validates pad availability
int load_pad_data(const char* pad_chksum, uint64_t offset, size_t length, unsigned char** pad_data) {
if (!pad_chksum || !pad_data) {
return 1; // Error: null pointer
}
char pad_path[1024];
char state_path[1024];
get_pad_path(pad_chksum, pad_path, state_path);
// Check if pad file exists
if (access(pad_path, R_OK) != 0) {
return 2; // Error: pad file not found
}
// Check pad file size
struct stat pad_stat;
if (stat(pad_path, &pad_stat) != 0) {
return 3; // Error: cannot get pad file size
}
if (offset + length > (uint64_t)pad_stat.st_size) {
return 4; // Error: not enough pad space
}
// Allocate memory for pad data
*pad_data = malloc(length);
if (!*pad_data) {
return 5; // Error: memory allocation failed
}
// Open and read pad file
FILE* pad_file = fopen(pad_path, "rb");
if (!pad_file) {
free(*pad_data);
*pad_data = NULL;
return 6; // Error: cannot open pad file
}
if (fseek(pad_file, offset, SEEK_SET) != 0) {
fclose(pad_file);
free(*pad_data);
*pad_data = NULL;
return 7; // Error: cannot seek to offset
}
if (fread(*pad_data, 1, length, pad_file) != length) {
fclose(pad_file);
free(*pad_data);
*pad_data = NULL;
return 8; // Error: cannot read pad data
}
fclose(pad_file);
return 0; // Success
}
// Universal ASCII armor generator - consolidates duplicate ASCII armor generation
// Creates ASCII armored output format used by both text and file encryption
int generate_ascii_armor(const char* chksum, uint64_t offset, const unsigned char* encrypted_data,
size_t data_length, char** ascii_output) {
if (!chksum || !encrypted_data || !ascii_output) {
return 1; // Error: null pointer
}
// Encode data as base64
char* base64_data = custom_base64_encode(encrypted_data, data_length);
if (!base64_data) {
return 2; // Error: base64 encoding failed
}
// Calculate required buffer size
size_t base64_len = strlen(base64_data);
size_t header_size = 200; // Approximate size for headers
size_t total_size = header_size + base64_len + (base64_len / 64) + 100; // +newlines +footer
*ascii_output = malloc(total_size);
if (!*ascii_output) {
free(base64_data);
return 3; // Error: memory allocation failed
}
// Build ASCII armor
strcpy(*ascii_output, "-----BEGIN OTP MESSAGE-----\n");
char temp_line[256];
snprintf(temp_line, sizeof(temp_line), "Version: v0.3.7\n");
strcat(*ascii_output, temp_line);
snprintf(temp_line, sizeof(temp_line), "Pad-ChkSum: %s\n", chksum);
strcat(*ascii_output, temp_line);
snprintf(temp_line, sizeof(temp_line), "Pad-Offset: %lu\n", offset);
strcat(*ascii_output, temp_line);
strcat(*ascii_output, "\n");
// Add base64 data in 64-character lines
int b64_len = strlen(base64_data);
for (int i = 0; i < b64_len; i += 64) {
char line[70];
snprintf(line, sizeof(line), "%.64s\n", base64_data + i);
strcat(*ascii_output, line);
}
strcat(*ascii_output, "-----END OTP MESSAGE-----\n");
free(base64_data);
return 0; // Success
}
// Universal pad integrity validator - consolidates pad validation logic
// Verifies pad checksum matches expected value for security
int validate_pad_integrity(const char* pad_path, const char* expected_chksum) {
if (!pad_path || !expected_chksum) {
return 1; // Error: null pointer
}
char current_chksum[MAX_HASH_LENGTH];
if (calculate_checksum(pad_path, current_chksum) != 0) {
return 2; // Error: cannot calculate checksum
}
if (strcmp(expected_chksum, current_chksum) != 0) {
return 3; // Error: checksum mismatch
}
return 0; // Success - pad integrity verified
}
// Universal decrypt function - consolidates all decrypt operations
// input_data: encrypted message text or file path
// output_target: output file path (NULL for stdout/interactive)
// mode: determines behavior and output format
int universal_decrypt(const char* input_data, const char* output_target, decrypt_mode_t mode) {
char stored_chksum[MAX_HASH_LENGTH];
uint64_t pad_offset;
char base64_data[MAX_INPUT_SIZE * 8] = {0};
unsigned char* ciphertext = NULL;
int ciphertext_len;
// Handle input based on mode
if (mode == DECRYPT_MODE_FILE_TO_TEXT || mode == DECRYPT_MODE_FILE_TO_FILE) {
// File input - read the entire file
FILE* input_fp = fopen(input_data, "r");
if (!input_fp) {
printf("Error: Cannot open input file %s\n", input_data);
return 1;
}
fseek(input_fp, 0, SEEK_END);
long file_size = ftell(input_fp);
fseek(input_fp, 0, SEEK_SET);
char* file_content = malloc(file_size + 1);
if (!file_content) {
printf("Error: Memory allocation failed\n");
fclose(input_fp);
return 1;
}
size_t bytes_read = fread(file_content, 1, file_size, input_fp);
file_content[bytes_read] = '\0';
fclose(input_fp);
// Parse ASCII message from file content
if (parse_ascii_message(file_content, stored_chksum, &pad_offset, base64_data) != 0) {
printf("Error: Invalid ASCII armored format in file\n");
free(file_content);
return 1;
}
free(file_content);
if (mode == DECRYPT_MODE_FILE_TO_TEXT) {
printf("Decrypting ASCII armored file...\n");
}
// Note: DECRYPT_MODE_FILE_TO_FILE should be completely silent for piping
} else {
// Text input (interactive or piped)
const char* message_text;
char full_message[MAX_INPUT_SIZE * 4] = {0};
if (input_data != NULL) {
message_text = input_data;
} else {
// Interactive mode - read from stdin
if (mode == DECRYPT_MODE_INTERACTIVE) {
printf("Enter encrypted message (paste the full ASCII armor block):\n");
}
char line[MAX_LINE_LENGTH];
while (fgets(line, sizeof(line), stdin)) {
strncat(full_message, line, sizeof(full_message) - strlen(full_message) - 1);
if (strstr(line, "-----END OTP MESSAGE-----")) {
break;
}
}
message_text = full_message;
}
// Parse ASCII message from text
if (parse_ascii_message(message_text, stored_chksum, &pad_offset, base64_data) != 0) {
if (mode == DECRYPT_MODE_SILENT) {
fprintf(stderr, "Error: Invalid message format - missing BEGIN header\n");
} else {
printf("Error: Invalid message format - missing BEGIN header\n");
}
return 1;
}
}
// Get pad path and check existence
char pad_path[MAX_HASH_LENGTH + 20];
char state_path[MAX_HASH_LENGTH + 20];
get_pad_path(stored_chksum, pad_path, state_path);
if (access(pad_path, R_OK) != 0) {
if (mode == DECRYPT_MODE_SILENT) {
fprintf(stderr, "Error: Required pad not found: %s\n", stored_chksum);
} else {
printf("Error: Required pad not found: %s\n", stored_chksum);
if (mode == DECRYPT_MODE_INTERACTIVE || mode == DECRYPT_MODE_FILE_TO_TEXT) {
printf("Available pads:\n");
char* selected = select_pad_interactive("Available pads:", "Available pads (press Enter to continue)", PAD_FILTER_ALL, 0);
if (selected) {
free(selected);
}
}
}
return 1;
}
// Validate pad integrity
int integrity_result = validate_pad_integrity(pad_path, stored_chksum);
if (integrity_result == 3) {
if (mode == DECRYPT_MODE_SILENT) {
fprintf(stderr, "Error: Pad integrity check failed!\n");
return 1;
} else if (mode == DECRYPT_MODE_INTERACTIVE) {
printf("Warning: Pad integrity check failed!\n");
printf("Expected: %s\n", stored_chksum);
printf("Continue anyway? (y/N): ");
fflush(stdout);
char response[10];
if (fgets(response, sizeof(response), stdin) == NULL ||
(response[0] != 'y' && response[0] != 'Y')) {
printf("Decryption aborted.\n");
return 1;
}
}
} else if (integrity_result != 0) {
if (mode == DECRYPT_MODE_SILENT) {
fprintf(stderr, "Error: Cannot verify pad integrity\n");
} else {
printf("Error: Cannot verify pad integrity\n");
}
return 1;
} else {
if (mode == DECRYPT_MODE_INTERACTIVE || mode == DECRYPT_MODE_FILE_TO_TEXT) {
printf("Pad integrity: VERIFIED\n");
}
}
// Decode base64 ciphertext
ciphertext = custom_base64_decode(base64_data, &ciphertext_len);
if (!ciphertext) {
if (mode == DECRYPT_MODE_SILENT) {
fprintf(stderr, "Error: Invalid base64 data\n");
} else {
printf("Error: Invalid base64 data\n");
}
return 1;
}
// Load pad data using universal function
unsigned char* pad_data;
if (load_pad_data(stored_chksum, pad_offset, ciphertext_len, &pad_data) != 0) {
if (mode == DECRYPT_MODE_SILENT) {
fprintf(stderr, "Error: Cannot load pad data\n");
} else {
printf("Error: Cannot load pad data\n");
}
free(ciphertext);
return 1;
}
// Decrypt using universal XOR operation
if (universal_xor_operation(ciphertext, ciphertext_len, pad_data, ciphertext) != 0) {
if (mode == DECRYPT_MODE_SILENT) {
fprintf(stderr, "Error: Decryption operation failed\n");
} else {
printf("Error: Decryption operation failed\n");
}
free(ciphertext);
free(pad_data);
return 1;
}
// Output based on mode
if (mode == DECRYPT_MODE_FILE_TO_FILE) {
// Write to output file
const char* output_file = output_target;
// Generate default output filename if not provided
char default_output[512];
if (output_file == NULL) {
strncpy(default_output, input_data, sizeof(default_output) - 1);
default_output[sizeof(default_output) - 1] = '\0';
char* ext = strstr(default_output, ".otp.asc");
if (ext) {
*ext = '\0';
} else {
strncat(default_output, ".decrypted", sizeof(default_output) - strlen(default_output) - 1);
}
output_file = default_output;
}
FILE* output_fp = fopen(output_file, "wb");
if (!output_fp) {
printf("Error: Cannot create output file %s\n", output_file);
free(ciphertext);
free(pad_data);
return 1;
}
if (fwrite(ciphertext, 1, ciphertext_len, output_fp) != (size_t)ciphertext_len) {
printf("Error: Cannot write decrypted data\n");
free(ciphertext);
free(pad_data);
fclose(output_fp);
return 1;
}
fclose(output_fp);
// Only show success messages in non-silent modes
if (mode != DECRYPT_MODE_FILE_TO_FILE) {
printf("File decrypted successfully: %s\n", output_file);
printf("Note: ASCII format does not preserve original filename/permissions\n");
}
} else {
// Text output to stdout - need to allocate space for null terminator
char* decrypted_text = malloc(ciphertext_len + 1);
if (!decrypted_text) {
printf("Error: Memory allocation failed for output\n");
free(ciphertext);
free(pad_data);
return 1;
}
memcpy(decrypted_text, ciphertext, ciphertext_len);
decrypted_text[ciphertext_len] = '\0';
if (mode == DECRYPT_MODE_SILENT) {
// Silent mode - just output the text
printf("%s\n", decrypted_text);
fflush(stdout);
} else {
// Interactive mode - with label
printf("Decrypted: %s\n", decrypted_text);
}
free(decrypted_text);
}
// Cleanup
free(ciphertext);
free(pad_data);
return 0;
}
// Enhanced input function with editable pre-filled text
int get_filename_with_default(const char* prompt, const char* default_path, char* result, size_t result_size) {
// Find the last directory separator
char* last_slash = strrchr(default_path, '/');
char directory[1024] = "";
char filename[512] = "";
if (last_slash) {
// Extract directory path
size_t dir_len = last_slash - default_path + 1; // Include the trailing slash
if (dir_len < sizeof(directory)) {
strncpy(directory, default_path, dir_len);
directory[dir_len] = '\0';
}
// Extract filename
strncpy(filename, last_slash + 1, sizeof(filename) - 1);
filename[sizeof(filename) - 1] = '\0';
} else {
// No directory separator, treat as filename only
strncpy(filename, default_path, sizeof(filename) - 1);
filename[sizeof(filename) - 1] = '\0';
}
// Setup terminal for raw input
struct termios orig_termios;
if (tcgetattr(STDIN_FILENO, &orig_termios) != 0) {
// Fallback to simple input if terminal control fails
printf("\n%s\n%s: ", prompt, directory);
fflush(stdout);
char input_buffer[512];
if (!fgets(input_buffer, sizeof(input_buffer), stdin)) {
return 1;
}
input_buffer[strcspn(input_buffer, "\n")] = 0;
if (strlen(input_buffer) == 0) {
strncpy(result, default_path, result_size - 1);
} else {
if (strlen(directory) > 0) {
snprintf(result, result_size, "%s%s", directory, input_buffer);
} else {
strncpy(result, input_buffer, result_size - 1);
}
}
result[result_size - 1] = '\0';
return 0;
}
// Set up raw terminal mode
struct termios raw_termios = orig_termios;
raw_termios.c_lflag &= ~(ECHO | ICANON);
raw_termios.c_cc[VMIN] = 1;
raw_termios.c_cc[VTIME] = 0;
if (tcsetattr(STDIN_FILENO, TCSANOW, &raw_termios) != 0) {
// Fallback if terminal setup fails
printf("\n%s\n%s: ", prompt, directory);
fflush(stdout);
char input_buffer[512];
if (!fgets(input_buffer, sizeof(input_buffer), stdin)) {
return 1;
}
input_buffer[strcspn(input_buffer, "\n")] = 0;
if (strlen(input_buffer) == 0) {
strncpy(result, default_path, result_size - 1);
} else {
if (strlen(directory) > 0) {
snprintf(result, result_size, "%s%s", directory, input_buffer);
} else {
strncpy(result, input_buffer, result_size - 1);
}
}
result[result_size - 1] = '\0';
return 0;
}
// Display prompt and directory
printf("\n%s\n%s", prompt, directory);
fflush(stdout);
// Initialize editing buffer with default filename
char edit_buffer[512];
strncpy(edit_buffer, filename, sizeof(edit_buffer) - 1);
edit_buffer[sizeof(edit_buffer) - 1] = '\0';
int cursor_pos = strlen(edit_buffer);
int buffer_len = cursor_pos;
// Display initial filename
printf("%s", edit_buffer);
fflush(stdout);
// Main editing loop
int c;
while ((c = getchar()) != EOF) {
if (c == '\n' || c == '\r') {
// Enter key - accept input
break;
} else if (c == 127 || c == 8) {
// Backspace/Delete
if (cursor_pos > 0) {
// Move everything after cursor one position left
memmove(&edit_buffer[cursor_pos - 1], &edit_buffer[cursor_pos], buffer_len - cursor_pos + 1);
cursor_pos--;
buffer_len--;
// Redraw line: move cursor to start of filename area, clear to end, redraw buffer
printf("\r%s\033[K%s", directory, edit_buffer);
// Position cursor correctly
if (cursor_pos < buffer_len) {
printf("\033[%dD", buffer_len - cursor_pos);
}
fflush(stdout);
}
} else if (c == 27) {
// Escape sequence (arrow keys, etc.)
int c1 = getchar();
int c2 = getchar();
if (c1 == '[') {
if (c2 == 'C' && cursor_pos < buffer_len) {
// Right arrow
cursor_pos++;
printf("\033[1C");
fflush(stdout);
} else if (c2 == 'D' && cursor_pos > 0) {
// Left arrow
cursor_pos--;
printf("\033[1D");
fflush(stdout);
} else if (c2 == 'H') {
// Home key
printf("\033[%dD", cursor_pos);
cursor_pos = 0;
fflush(stdout);
} else if (c2 == 'F') {
// End key
printf("\033[%dC", buffer_len - cursor_pos);
cursor_pos = buffer_len;
fflush(stdout);
}
}
} else if (c >= 32 && c <= 126) {
// Printable character
if (buffer_len < (int)sizeof(edit_buffer) - 1) {
// Move everything after cursor one position right
memmove(&edit_buffer[cursor_pos + 1], &edit_buffer[cursor_pos], buffer_len - cursor_pos + 1);
edit_buffer[cursor_pos] = c;
cursor_pos++;
buffer_len++;
// Redraw from cursor position
printf("%c", c);
if (cursor_pos < buffer_len) {
// Print remaining characters and move cursor back
printf("%s\033[%dD", &edit_buffer[cursor_pos], buffer_len - cursor_pos);
}
fflush(stdout);
}
}
// Ignore other control characters
}
// Restore terminal
tcsetattr(STDIN_FILENO, TCSANOW, &orig_termios);
printf("\n");
// Construct final result
if (buffer_len == 0) {
// Empty input, use default
strncpy(result, default_path, result_size - 1);
} else {
// Combine directory with edited filename
edit_buffer[buffer_len] = '\0';
if (strlen(directory) > 0) {
snprintf(result, result_size, "%s%s", directory, edit_buffer);
} else {
strncpy(result, edit_buffer, result_size - 1);
}
}
result[result_size - 1] = '\0';
return 0;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// EDITOR AND FILE MANAGER
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
char* get_preferred_editor(void) {
// Check EDITOR environment variable first
char* editor = getenv("EDITOR");
if (editor && strlen(editor) > 0) {
// Verify the editor exists
char command[512];
snprintf(command, sizeof(command), "which %s >/dev/null 2>&1", editor);
if (system(command) == 0) {
return strdup(editor);
}
}
// Check VISUAL environment variable
editor = getenv("VISUAL");
if (editor && strlen(editor) > 0) {
char command[512];
snprintf(command, sizeof(command), "which %s >/dev/null 2>&1", editor);
if (system(command) == 0) {
return strdup(editor);
}
}
// Try common editors in order of preference
const char* common_editors[] = {"vim", "vi", "nano", "emacs", "gedit", NULL};
for (int i = 0; common_editors[i] != NULL; i++) {
char command[512];
snprintf(command, sizeof(command), "which %s >/dev/null 2>&1", common_editors[i]);
if (system(command) == 0) {
return strdup(common_editors[i]);
}
}
return NULL; // No editor found
}
char* get_preferred_file_manager(void) {
// Try file managers in order of preference
const char* file_managers[] = {"ranger", "fzf", "nnn", "lf", NULL};
for (int i = 0; file_managers[i] != NULL; i++) {
char command[512];
snprintf(command, sizeof(command), "which %s >/dev/null 2>&1", file_managers[i]);
if (system(command) == 0) {
return strdup(file_managers[i]);
}
}
return NULL; // No file manager found
}
int launch_text_editor(const char* initial_content, char* result_buffer, size_t buffer_size) {
char* editor = get_preferred_editor();
if (!editor) {
printf("Error: No text editor found. Set EDITOR environment variable or install vim/nano.\n");
return 1;
}
// Create temporary file
char temp_filename[64];
snprintf(temp_filename, sizeof(temp_filename), "/tmp/otp_edit_%ld.tmp", time(NULL));
// Write initial content to temp file if provided
if (initial_content && strlen(initial_content) > 0) {
FILE* temp_file = fopen(temp_filename, "w");
if (temp_file) {
fputs(initial_content, temp_file);
fclose(temp_file);
}
} else {
// Create empty temp file
FILE* temp_file = fopen(temp_filename, "w");
if (temp_file) {
fclose(temp_file);
}
}
// Launch editor
printf("Opening %s for text editing...\n", editor);
char command[512];
snprintf(command, sizeof(command), "%s %s", editor, temp_filename);
int result = system(command);
if (result == 0) {
// Read the edited content back
FILE* temp_file = fopen(temp_filename, "r");
if (temp_file) {
size_t bytes_read = fread(result_buffer, 1, buffer_size - 1, temp_file);
result_buffer[bytes_read] = '\0';
// Remove trailing newline if present
if (bytes_read > 0 && result_buffer[bytes_read - 1] == '\n') {
result_buffer[bytes_read - 1] = '\0';
}
fclose(temp_file);
} else {
printf("Error: Cannot read edited content\n");
free(editor);
unlink(temp_filename);
return 1;
}
} else {
printf("Editor exited with error or was cancelled\n");
free(editor);
unlink(temp_filename);
return 1;
}
// Clean up
unlink(temp_filename);
free(editor);
return 0;
}
int launch_file_manager(const char* start_directory, char* selected_file, 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 file path entry.\n");
return 1; // Fall back to manual entry
}
char temp_filename[64];
snprintf(temp_filename, sizeof(temp_filename), "/tmp/otp_file_%ld.tmp", time(NULL));
char command[512];
int result = 1;
printf("Opening %s for file selection...\n", fm);
if (strcmp(fm, "ranger") == 0) {
snprintf(command, sizeof(command), "cd '%s' && ranger --choosefile=%s",
start_directory ? start_directory : ".", temp_filename);
} else if (strcmp(fm, "fzf") == 0) {
snprintf(command, sizeof(command), "cd '%s' && find . -type f | 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 file from temp file
FILE* temp_file = fopen(temp_filename, "r");
if (temp_file) {
if (fgets(selected_file, buffer_size, temp_file)) {
// Remove trailing newline
selected_file[strcspn(selected_file, "\n\r")] = 0;
// For relative paths from fzf, make absolute if needed
if (selected_file[0] == '.' && selected_file[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_file + 2);
strncpy(selected_file, abs_path, buffer_size - 1);
selected_file[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
}
int handle_text_encrypt(void) {
printf("\n=== Text Encrypt ===\n");
// Launch text editor directly
char text_buffer[MAX_INPUT_SIZE];
if (launch_text_editor(NULL, text_buffer, sizeof(text_buffer)) != 0) {
printf("Error: Could not launch text editor\n");
return 1;
}
if (strlen(text_buffer) == 0) {
printf("No text entered - canceling encryption\n");
return 1;
}
// Use unified pad selection
char* selected_pad = select_pad_interactive("=== Select Pad for Text Encryption ===",
"Select pad (by prefix)",
PAD_FILTER_ALL, 1);
if (!selected_pad) {
printf("Text encryption cancelled.\n");
return 1;
}
int result = encrypt_text(selected_pad, text_buffer);
free(selected_pad);
return result;
}
int handle_file_encrypt(void) {
printf("\n=== File Encrypt ===\n");
// Launch file manager directly
char input_file[512];
if (launch_file_manager(".", input_file, sizeof(input_file)) != 0) {
printf("Error: Could not launch file manager\n");
return 1;
}
// Check if file exists
if (access(input_file, R_OK) != 0) {
printf("Error: File '%s' not found or cannot be read\n", input_file);
return 1;
}
// Use unified pad selection
char* selected_pad = select_pad_interactive("=== Select Pad for File Encryption ===",
"Select pad (by prefix)",
PAD_FILTER_ALL, 1);
if (!selected_pad) {
printf("File encryption cancelled.\n");
return 1;
}
// Ask for output format
printf("\nSelect output format:\n");
printf("1. Binary (.otp) - preserves file permissions\n");
printf("2. ASCII (.otp.asc) - text-safe format\n");
printf("Enter choice (1-2): ");
char format_input[10];
if (!fgets(format_input, sizeof(format_input), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
int ascii_armor = (atoi(format_input) == 2) ? 1 : 0;
// Generate default output filename
char default_output[1024]; // Increased buffer size to prevent truncation warnings
if (ascii_armor) {
snprintf(default_output, sizeof(default_output), "%s.otp.asc", input_file);
} else {
snprintf(default_output, sizeof(default_output), "%s.otp", input_file);
}
// Use enhanced input function for 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");
return 1;
}
const char* output_filename = output_file;
int result = encrypt_file(selected_pad, input_file, output_filename, ascii_armor);
free(selected_pad);
return result;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// PADS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
int calculate_checksum(const char* filename, char* checksum_hex) {
FILE* file = fopen(filename, "rb");
if (!file) {
return 1;
}
unsigned char checksum[32];
unsigned char buffer[64 * 1024]; // 64KB buffer for large files
size_t bytes_read;
// Initialize checksum
memset(checksum, 0, 32);
size_t total_bytes = 0;
// Calculate XOR checksum of entire file
while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) {
// Process this chunk with XOR checksum
for (size_t i = 0; i < bytes_read; i++) {
unsigned char bucket = (total_bytes + i) % 32;
checksum[bucket] ^= buffer[i] ^ (((total_bytes + i) >> 8) & 0xFF) ^
(((total_bytes + i) >> 16) & 0xFF) ^ (((total_bytes + i) >> 24) & 0xFF);
}
total_bytes += bytes_read;
}
fclose(file);
// Now encrypt the checksum with the first 32 bytes of the pad
fseek(file = fopen(filename, "rb"), 0, SEEK_SET);
unsigned char pad_key[32];
if (fread(pad_key, 1, 32, file) != 32) {
fclose(file);
return 1;
}
fclose(file);
// XOR encrypt the checksum with pad data to create unique identifier
unsigned char encrypted_checksum[32];
for (int i = 0; i < 32; i++) {
encrypted_checksum[i] = checksum[i] ^ pad_key[i];
}
// Convert to hex string (64 characters)
for (int i = 0; i < 32; i++) {
sprintf(checksum_hex + (i * 2), "%02x", encrypted_checksum[i]);
}
checksum_hex[64] = '\0';
return 0;
}
// Unified pad selection function - extracts the best UI from handle_pads_menu()
char* select_pad_interactive(const char* title, const char* prompt, pad_filter_type_t filter_type, int allow_cancel) {
// Get list of pads from current directory
DIR* dir = opendir(current_pads_dir);
if (!dir) {
printf("Error: Cannot open pads directory %s\n", current_pads_dir);
return NULL;
}
// Structure to store pad information
struct PadInfo {
char chksum[65];
char size_str[32];
char used_str[32];
double percentage;
char location[256];
};
struct PadInfo pads[100]; // Support up to 100 pads
int pad_count = 0;
// Collect all pad information
struct dirent* entry;
while ((entry = readdir(dir)) != NULL && pad_count < 100) {
if (strstr(entry->d_name, ".pad") && strlen(entry->d_name) == 68) {
strncpy(pads[pad_count].chksum, entry->d_name, 64);
pads[pad_count].chksum[64] = '\0';
// Get pad file size and usage info
char full_path[1024];
snprintf(full_path, sizeof(full_path), "%s/%s", current_pads_dir, entry->d_name);
struct stat st;
if (stat(full_path, &st) == 0) {
// Get used bytes from state
uint64_t used_bytes;
read_state_offset(pads[pad_count].chksum, &used_bytes);
// Apply filter
if (filter_type == PAD_FILTER_UNUSED_ONLY && used_bytes > 32) {
continue; // Skip used pads when filtering for unused only
}
// Format total size
if (st.st_size < 1024) {
snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%luB", st.st_size);
} else if (st.st_size < 1024 * 1024) {
snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.1fKB", (double)st.st_size / 1024.0);
} else if (st.st_size < 1024 * 1024 * 1024) {
snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.1fMB", (double)st.st_size / (1024.0 * 1024.0));
} else {
snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.2fGB", (double)st.st_size / (1024.0 * 1024.0 * 1024.0));
}
// Format used size
if (used_bytes < 1024) {
snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%luB", used_bytes);
} else if (used_bytes < 1024 * 1024) {
snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.1fKB", (double)used_bytes / 1024.0);
} else if (used_bytes < 1024 * 1024 * 1024) {
snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.1fMB", (double)used_bytes / (1024.0 * 1024.0));
} else {
snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.2fGB", (double)used_bytes / (1024.0 * 1024.0 * 1024.0));
}
// Calculate percentage
pads[pad_count].percentage = (double)used_bytes / st.st_size * 100.0;
// Set location info using directory display
get_directory_display(full_path, pads[pad_count].location, sizeof(pads[pad_count].location));
pad_count++;
}
}
}
closedir(dir);
if (pad_count == 0) {
printf("\n%s\n", title);
if (filter_type == PAD_FILTER_UNUSED_ONLY) {
printf("No unused pads found.\n");
printf("Entropy can only be added to pads with 0%% usage (only reserved bytes used).\n");
} else {
printf("No pads found.\n");
}
return NULL;
}
// Calculate minimal unique prefixes for each pad
char prefixes[100][65];
int prefix_lengths[100];
for (int i = 0; i < pad_count; i++) {
prefix_lengths[i] = 1;
// Find minimal unique prefix
while (prefix_lengths[i] <= 64) {
int unique = 1;
// Check if current prefix is unique among all other pads
for (int j = 0; j < pad_count; j++) {
if (i != j && strncmp(pads[i].chksum, pads[j].chksum, prefix_lengths[i]) == 0) {
unique = 0;
break;
}
}
if (unique) {
break;
}
prefix_lengths[i]++;
}
// Store the minimal prefix
strncpy(prefixes[i], pads[i].chksum, prefix_lengths[i]);
prefixes[i][prefix_lengths[i]] = '\0';
}
// Display title and pads table
printf("\n%s\n", title);
printf("%-8s %-2s %-12s %-12s %-12s %-8s\n", "ChkSum", "D", "Dir", "Size", "Used", "% Used");
printf("%-8s %-2s %-12s %-12s %-12s %-8s\n", "--------", "--", "------------", "----------", "----------", "------");
// Get current default pad path for comparison
char* current_default = get_default_pad_path();
char default_pad_checksum[65] = "";
if (current_default) {
// Extract checksum from default pad path
char* filename = strrchr(current_default, '/');
if (!filename) filename = current_default;
else filename++; // Skip the '/'
// Extract checksum (remove .pad extension)
if (strlen(filename) >= 68 && strstr(filename, ".pad")) {
strncpy(default_pad_checksum, filename, 64);
default_pad_checksum[64] = '\0';
}
free(current_default);
}
for (int i = 0; i < pad_count; i++) {
// Check if this is the default pad
int is_default = (strlen(default_pad_checksum) > 0 &&
strncmp(pads[i].chksum, default_pad_checksum, 64) == 0);
// Display first 8 characters of checksum with prefix underlined
char checksum_8char[9];
strncpy(checksum_8char, pads[i].chksum, 8);
checksum_8char[8] = '\0';
printf("\033[4m%.*s\033[0m%s %-2s %-12s %-12s %-12s %.1f%%\n",
prefix_lengths[i], checksum_8char, // Underlined prefix
checksum_8char + prefix_lengths[i], // Rest of 8-char checksum
is_default ? "*" : "", // Default indicator
pads[i].location,
pads[i].size_str,
pads[i].used_str,
pads[i].percentage);
}
// Display prompt
printf("\n%s", prompt);
if (allow_cancel) {
printf(" (or 'x' to cancel)");
}
printf(": ");
char input[MAX_HASH_LENGTH];
if (!fgets(input, sizeof(input), stdin)) {
printf("Error: Failed to read input\n");
return NULL;
}
input[strcspn(input, "\n")] = 0;
// Handle empty input - select default pad if available
if (strlen(input) == 0) {
// Get current default pad path
char* current_default = get_default_pad_path();
if (current_default) {
// Extract checksum from default pad path
char* filename = strrchr(current_default, '/');
if (!filename) filename = current_default;
else filename++; // Skip the '/'
// Extract checksum (remove .pad extension)
if (strlen(filename) >= 68 && strstr(filename, ".pad")) {
char default_checksum[65];
strncpy(default_checksum, filename, 64);
default_checksum[64] = '\0';
// Verify this default pad is in our current list
for (int i = 0; i < pad_count; i++) {
if (strncmp(pads[i].chksum, default_checksum, 64) == 0) {
free(current_default);
printf("Selected default pad: %.16s...\n\n", default_checksum);
return strdup(default_checksum);
}
}
}
free(current_default);
}
// No default pad or default pad not in current list
printf("No default pad available or default pad not in current list\n");
return NULL;
}
// Handle cancel
if (allow_cancel && (toupper(input[0]) == 'X' && strlen(input) == 1)) {
return NULL;
}
// Find matching pad by prefix only
int selected_pad = -1;
int match_count = 0;
// Try prefix matching only
for (int i = 0; i < pad_count; i++) {
if (strncmp(input, pads[i].chksum, strlen(input)) == 0) {
if (match_count == 0) {
selected_pad = i;
}
match_count++;
}
}
if (match_count == 0) {
printf("No pad found matching '%s'\n", input);
return NULL;
} else if (match_count > 1) {
printf("Ambiguous prefix. Multiple matches found.\n");
return NULL;
}
// Return selected pad checksum (caller must free)
return strdup(pads[selected_pad].chksum);
}
int handle_pads_menu(void) {
printf("\n=== Pad Management ===\n");
// Get list of pads from current directory
DIR* dir = opendir(current_pads_dir);
if (!dir) {
printf("Error: Cannot open pads directory %s\n", current_pads_dir);
return 1;
}
// Structure to store pad information
struct PadInfo {
char chksum[65];
char size_str[32];
char used_str[32];
double percentage;
char location[256]; // Store location info
};
struct PadInfo pads[100]; // Support up to 100 pads
int pad_count = 0;
// Collect all pad information
struct dirent* entry;
while ((entry = readdir(dir)) != NULL && pad_count < 100) {
if (strstr(entry->d_name, ".pad") && strlen(entry->d_name) == 68) {
strncpy(pads[pad_count].chksum, entry->d_name, 64);
pads[pad_count].chksum[64] = '\0';
// Get pad file size and usage info
char full_path[1024]; // Increased buffer size
snprintf(full_path, sizeof(full_path), "%s/%s", current_pads_dir, entry->d_name);
struct stat st;
if (stat(full_path, &st) == 0) {
// Get used bytes from state
uint64_t used_bytes;
read_state_offset(pads[pad_count].chksum, &used_bytes);
// Format total size
if (st.st_size < 1024) {
snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%luB", st.st_size);
} else if (st.st_size < 1024 * 1024) {
snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.1fKB", (double)st.st_size / 1024.0);
} else if (st.st_size < 1024 * 1024 * 1024) {
snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.1fMB", (double)st.st_size / (1024.0 * 1024.0));
} else {
snprintf(pads[pad_count].size_str, sizeof(pads[pad_count].size_str), "%.2fGB", (double)st.st_size / (1024.0 * 1024.0 * 1024.0));
}
// Format used size
if (used_bytes < 1024) {
snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%luB", used_bytes);
} else if (used_bytes < 1024 * 1024) {
snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.1fKB", (double)used_bytes / 1024.0);
} else if (used_bytes < 1024 * 1024 * 1024) {
snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.1fMB", (double)used_bytes / (1024.0 * 1024.0));
} else {
snprintf(pads[pad_count].used_str, sizeof(pads[pad_count].used_str), "%.2fGB", (double)used_bytes / (1024.0 * 1024.0 * 1024.0));
}
// Calculate percentage
pads[pad_count].percentage = (double)used_bytes / st.st_size * 100.0;
// Set location info using directory display
get_directory_display(full_path, pads[pad_count].location, sizeof(pads[pad_count].location));
pad_count++;
}
}
}
closedir(dir);
if (pad_count == 0) {
printf("No pads found.\n");
printf("\nOptions:\n");
printf(" \033[4mG\033[0menerate new pad\n");
printf(" E\033[4mx\033[0mit\n");
printf("\nSelect option: ");
char input[10];
if (fgets(input, sizeof(input), stdin)) {
char choice = toupper(input[0]);
if (choice == 'G') {
int result = handle_generate_menu();
if (result == 0) {
// After successful pad generation, return to pads menu
return handle_pads_menu();
}
return result;
}
}
return 0;
}
// Calculate minimal unique prefixes for each pad
char prefixes[100][65]; // Store the minimal prefix for each pad
int prefix_lengths[100]; // Length of minimal prefix for each pad
for (int i = 0; i < pad_count; i++) {
prefix_lengths[i] = 1;
// Find minimal unique prefix
while (prefix_lengths[i] <= 64) {
int unique = 1;
// Check if current prefix is unique among all other pads
for (int j = 0; j < pad_count; j++) {
if (i != j && strncmp(pads[i].chksum, pads[j].chksum, prefix_lengths[i]) == 0) {
unique = 0;
break;
}
}
if (unique) {
break;
}
prefix_lengths[i]++;
}
// Store the minimal prefix
strncpy(prefixes[i], pads[i].chksum, prefix_lengths[i]);
prefixes[i][prefix_lengths[i]] = '\0';
}
// Display pads with minimal prefixes underlined and default indicator
printf("\nAvailable pads:\n");
printf("%-8s %-2s %-12s %-12s %-12s %-8s\n", "ChkSum", "D", "Dir", "Size", "Used", "% Used");
printf("%-8s %-2s %-12s %-12s %-12s %-8s\n", "--------", "--", "------------", "----------", "----------", "------");
// Get current default pad path for comparison
char* current_default = get_default_pad_path();
char default_pad_checksum[65] = "";
if (current_default) {
// Extract checksum from default pad path
char* filename = strrchr(current_default, '/');
if (!filename) filename = current_default;
else filename++; // Skip the '/'
// Extract checksum (remove .pad extension)
if (strlen(filename) >= 68 && strstr(filename, ".pad")) {
strncpy(default_pad_checksum, filename, 64);
default_pad_checksum[64] = '\0';
}
free(current_default);
}
for (int i = 0; i < pad_count; i++) {
// Check if this is the default pad
int is_default = (strlen(default_pad_checksum) > 0 &&
strncmp(pads[i].chksum, default_pad_checksum, 64) == 0);
// Display first 8 characters of checksum with prefix underlined
char checksum_8char[9];
strncpy(checksum_8char, pads[i].chksum, 8);
checksum_8char[8] = '\0';
printf("\033[4m%.*s\033[0m%s %-2s %-12s %-12s %-12s %.1f%%\n",
prefix_lengths[i], checksum_8char, // Underlined prefix
checksum_8char + prefix_lengths[i], // Rest of 8-char checksum
is_default ? "*" : "", // Default indicator
pads[i].location, // Use the stored location info
pads[i].size_str,
pads[i].used_str,
pads[i].percentage);
}
printf("\nActions:\n");
printf(" \033[4mG\033[0menerate new pad\n");
printf(" \033[4mA\033[0mdd entropy to pad\n");
printf(" \033[4mS\033[0met default pad\n");
printf(" \033[4mU\033[0mSB drive management\n");
printf(" E\033[4mx\033[0mit\n");
printf("\nSelect action: ");
char input[MAX_HASH_LENGTH];
if (!fgets(input, sizeof(input), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
input[strcspn(input, "\n")] = 0;
// Handle actions
if (toupper(input[0]) == 'G') {
int result = handle_generate_menu();
if (result == 0) {
// After successful pad generation, return to pads menu
return handle_pads_menu();
}
return result;
} else if (toupper(input[0]) == 'A') {
// Add entropy to pad - use unified function with unused pads filter
char* selected_pad = select_pad_interactive("=== Select Unused Pad for Entropy Addition ===",
"Select unused pad (by prefix)",
PAD_FILTER_UNUSED_ONLY, 1);
if (!selected_pad) {
printf("Entropy addition cancelled.\n");
return handle_pads_menu();
}
// Add entropy to the selected unused pad
int result = handle_add_entropy_to_pad(selected_pad);
free(selected_pad);
if (result == 0) {
// Return to pads menu after successful entropy addition
return handle_pads_menu();
}
return result;
} else if (toupper(input[0]) == 'S') {
// Set default pad - use unified function
char* selected_pad = select_pad_interactive("=== Select Default Pad ===",
"Select pad to set as default (by prefix)",
PAD_FILTER_ALL, 1);
if (!selected_pad) {
printf("Default pad selection cancelled.\n");
return handle_pads_menu();
}
// Construct the full absolute pad path and set as default
char new_default_path[1024];
if (current_pads_dir[0] == '/') {
// Already absolute path
int ret = snprintf(new_default_path, sizeof(new_default_path), "%s/%s.pad", current_pads_dir, selected_pad);
if (ret >= (int)sizeof(new_default_path)) {
printf("Error: Path too long for default pad setting\n");
free(selected_pad);
return handle_pads_menu();
}
} else {
// Relative path - make it absolute
char current_dir[512];
if (getcwd(current_dir, sizeof(current_dir))) {
int ret = snprintf(new_default_path, sizeof(new_default_path), "%s/%s/%s.pad", current_dir, current_pads_dir, selected_pad);
if (ret >= (int)sizeof(new_default_path)) {
// Path was truncated, fall back to relative path
int ret2 = snprintf(new_default_path, sizeof(new_default_path), "%s/%s.pad", current_pads_dir, selected_pad);
if (ret2 >= (int)sizeof(new_default_path)) {
printf("Error: Path too long for default pad setting\n");
free(selected_pad);
return handle_pads_menu();
}
}
} else {
// Fallback to relative path
int ret = snprintf(new_default_path, sizeof(new_default_path), "%s/%s.pad", current_pads_dir, selected_pad);
if (ret >= (int)sizeof(new_default_path)) {
printf("Error: Path too long for default pad setting\n");
free(selected_pad);
return handle_pads_menu();
}
}
}
if (set_default_pad_path(new_default_path) == 0) {
printf("Default pad set to: %.16s...\n", selected_pad);
printf("Full path: %s\n", new_default_path);
} else {
printf("Error: Failed to update default pad preference\n");
}
free(selected_pad);
return handle_pads_menu();
} else if (toupper(input[0]) == 'U') {
// USB drive management
int result = handle_usb_submenu();
if (result == 0) {
return handle_pads_menu(); // Return to pads menu
}
return result;
} else if (toupper(input[0]) == 'X') {
return 0; // Exit to main menu
} else {
printf("Invalid action. Please select G, A, S, U, or X.\n");
return handle_pads_menu();
}
}
void get_directory_display(const char* file_path, char* result, size_t result_size) {
// Extract directory path from full file path
char dir_path[512];
char* last_slash = strrchr(file_path, '/');
if (last_slash) {
size_t dir_len = last_slash - file_path;
if (dir_len >= sizeof(dir_path)) {
dir_len = sizeof(dir_path) - 1;
}
strncpy(dir_path, file_path, dir_len);
dir_path[dir_len] = '\0';
} else {
// No directory separator, assume current directory
strcpy(dir_path, ".");
}
// USB Drive Detection and Smart Shortening
char* home_dir = getenv("HOME");
// Check for USB/removable media mount patterns
if (strstr(dir_path, "/media/") || strstr(dir_path, "/run/media/") || strstr(dir_path, "/mnt/")) {
// Extract USB label/name
char* media_start = NULL;
if (strstr(dir_path, "/media/")) {
media_start = strstr(dir_path, "/media/");
} else if (strstr(dir_path, "/run/media/")) {
media_start = strstr(dir_path, "/run/media/");
} else if (strstr(dir_path, "/mnt/")) {
media_start = strstr(dir_path, "/mnt/");
}
if (media_start) {
// Find the USB label part
char* path_after_media = strchr(media_start + 1, '/');
if (path_after_media) {
path_after_media++; // Skip the slash
// For /media/user/LABEL pattern, skip the username to get to the drive label
if (strstr(media_start, "/media/")) {
char* next_slash = strchr(path_after_media, '/');
if (next_slash) {
path_after_media = next_slash + 1;
}
}
// For /run/media/user/LABEL pattern, skip the username
else if (strstr(media_start, "/run/media/")) {
char* next_slash = strchr(path_after_media, '/');
if (next_slash) {
path_after_media = next_slash + 1;
}
}
// Extract just the USB label (up to next slash or end)
char* label_end = strchr(path_after_media, '/');
char usb_label[32];
if (label_end) {
size_t label_len = label_end - path_after_media;
if (label_len > sizeof(usb_label) - 1) label_len = sizeof(usb_label) - 1;
strncpy(usb_label, path_after_media, label_len);
usb_label[label_len] = '\0';
} else {
// USB label is the last part
strncpy(usb_label, path_after_media, sizeof(usb_label) - 1);
usb_label[sizeof(usb_label) - 1] = '\0';
}
// Format with USB: prefix, limiting total length to fit in result
snprintf(result, result_size, "USB:%s", usb_label);
// Truncate if too long
if (strlen(result) > 11) {
result[11] = '\0';
}
return;
}
}
}
// Home directory shortening
if (home_dir && strncmp(dir_path, home_dir, strlen(home_dir)) == 0) {
if (dir_path[strlen(home_dir)] == '/' || dir_path[strlen(home_dir)] == '\0') {
// Replace home directory with ~
char temp[512];
snprintf(temp, sizeof(temp), "~%s", dir_path + strlen(home_dir));
// If result is too long, truncate intelligently
if (strlen(temp) > 11) {
// Show ~/...end_part
char* last_part = strrchr(temp, '/');
if (last_part && strlen(last_part) < 8) {
snprintf(result, result_size, "~...%s", last_part);
} else {
strncpy(result, temp, 11);
result[11] = '\0';
}
} else {
strncpy(result, temp, result_size - 1);
result[result_size - 1] = '\0';
}
return;
}
}
// Current working directory
if (strcmp(dir_path, ".") == 0 || strcmp(dir_path, current_pads_dir) == 0) {
strncpy(result, "pads", result_size - 1);
result[result_size - 1] = '\0';
return;
}
// System/other paths - smart truncation with ellipsis
if (strlen(dir_path) > 11) {
// Try to show the most meaningful part
char* last_part = strrchr(dir_path, '/');
if (last_part && strlen(last_part) < 9) {
// Show .../last_part
snprintf(result, result_size, "...%s", last_part);
} else {
// Show first part with ellipsis
strncpy(result, dir_path, 8);
strncpy(result + 8, "...", result_size - 8 - 1);
result[result_size - 1] = '\0';
}
} else {
// Short enough, use as-is
strncpy(result, dir_path, result_size - 1);
result[result_size - 1] = '\0';
}
}
int handle_add_entropy_to_pad(const char* pad_chksum) {
printf("\n=== Add Entropy to Pad: %.16s... ===\n", pad_chksum);
// Present entropy source selection menu with consistent formatting
printf("Select entropy source:\n");
printf(" \033[4mK\033[0meyboard entropy - Random typing for entropy collection\n");
printf(" \033[4mD\033[0mice rolls - Manual dice roll input for high-quality entropy\n");
printf(" \033[4mT\033[0mrueRNG devices - Hardware random number generators\n");
printf(" E\033[4mx\033[0mit\n");
printf("\nSelect option: ");
char source_input[10];
if (!fgets(source_input, sizeof(source_input), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
char choice = toupper(source_input[0]);
entropy_source_t entropy_source;
switch (choice) {
case 'K':
entropy_source = ENTROPY_SOURCE_KEYBOARD;
break;
case 'D':
entropy_source = ENTROPY_SOURCE_DICE;
break;
case 'T':
entropy_source = ENTROPY_SOURCE_TRUERNG;
break;
case 'X':
return 0; // Exit
default:
printf("Invalid choice. Please select K, D, T, or X.\n");
return 1;
}
// Get entropy amount
printf("\nEntropy collection options:\n");
printf(" 1. Recommended (2048 bytes) - Optimal security\n");
printf(" 2. Minimum (1024 bytes) - Good security\n");
printf(" 3. Maximum (4096 bytes) - Maximum security\n");
printf(" 4. Custom amount\n");
printf("Enter choice (1-4): ");
char amount_input[10];
if (!fgets(amount_input, sizeof(amount_input), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
size_t target_bytes = 2048; // Default
int amount_choice = atoi(amount_input);
switch (amount_choice) {
case 1:
target_bytes = 2048;
break;
case 2:
target_bytes = 1024;
break;
case 3:
target_bytes = 4096;
break;
case 4:
printf("Enter custom amount (512-8192 bytes): ");
char custom_input[32];
if (!fgets(custom_input, sizeof(custom_input), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
size_t custom_amount = (size_t)atoi(custom_input);
if (custom_amount < 512 || custom_amount > 8192) {
printf("Error: Invalid amount. Must be between 512 and 8192 bytes.\n");
return 1;
}
target_bytes = custom_amount;
break;
default:
target_bytes = 2048; // Default to recommended
break;
}
printf("\nCollecting %zu bytes of entropy from selected source...\n", target_bytes);
// Allocate entropy buffer
unsigned char* entropy_buffer = malloc(MAX_ENTROPY_BUFFER);
if (!entropy_buffer) {
printf("Error: Cannot allocate entropy buffer\n");
return 1;
}
// Collect entropy using unified interface
size_t collected_bytes = 0;
int result = collect_entropy_by_source(entropy_source, entropy_buffer, target_bytes, &collected_bytes, 1);
if (result != 0) {
printf("Error: Entropy collection failed\n");
free(entropy_buffer);
return 1;
}
if (collected_bytes < 512) {
printf("Error: Insufficient entropy collected (%zu bytes)\n", collected_bytes);
free(entropy_buffer);
return 1;
}
printf("\nProcessing entropy and modifying pad...\n");
// Add entropy to pad
result = add_entropy_to_pad(pad_chksum, entropy_buffer, collected_bytes, 1);
// Clear entropy buffer for security
memset(entropy_buffer, 0, MAX_ENTROPY_BUFFER);
free(entropy_buffer);
if (result != 0) {
printf("Error: Failed to add entropy to pad\n");
return 1;
}
printf("\n🎉 SUCCESS! Your pad now has enhanced randomness!\n");
printf("Press Enter to continue...");
getchar();
return 0;
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// USB MENU HANDLERS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
int handle_usb_submenu(void) {
printf("\n=== USB Drive Management ===\n");
// Auto-display USB drives above the menu (like Pad Management does)
list_usb_drives_v2();
printf("\nActions:\n");
printf(" \033[4mI\033[0mnitialize USB drive\n");
printf(" \033[4mC\033[0mopy pad to USB\n");
printf(" \033[4mM\033[0mport pad from USB\n");
printf(" \033[4mD\033[0muplicate USB drive\n");
printf(" \033[4mV\033[0merify drive integrity\n");
printf(" E\033[4mx\033[0mit\n");
printf("\nSelect action: ");
char input[10];
if (!fgets(input, sizeof(input), stdin)) {
printf("Error: Failed to read input\n");
return 1;
}
char choice = toupper(input[0]);
switch (choice) {
case 'I':
return handle_initialize_usb();
case 'C':
return handle_copy_pad_to_usb();
case 'M':
return handle_import_pad_from_usb();
case 'D':
return handle_duplicate_usb_drive();
case 'V':
return handle_verify_usb_drive();
case 'X':
case 'Q':
return 0; // Exit to pads menu
default:
printf("Invalid choice. Please try again.\n");
return handle_usb_submenu();
}
}
int handle_initialize_usb(void) {
printf("\n=== Initialize USB Drive for OTP ===\n");
printf("This will prepare a USB drive for storing OTP pads.\n\n");
// Select USB drive
usb_drive_info_t* selected_drive = select_usb_drive_interactive(
"Available USB Drives", "Select drive to initialize", 0);
if (!selected_drive) {
printf("USB initialization cancelled.\n");
return handle_usb_submenu();
}
printf("\nSelected drive: %s (%s)\n", selected_drive->volume_label, selected_drive->mount_path);
// Get drive name
char drive_name[64];
printf("Enter OTP drive name (must start with 'OTP_'): ");
if (!fgets(drive_name, sizeof(drive_name), stdin)) {
printf("Error: Failed to read input\n");
free(selected_drive);
return 1;
}
drive_name[strcspn(drive_name, "\n")] = 0;
// Validate drive name
if (strncmp(drive_name, "OTP_", 4) != 0) {
printf("Error: Drive name must start with 'OTP_'\n");
free(selected_drive);
return handle_usb_submenu();
}
// Ask about formatting
printf("\nFormat options:\n");
printf(" 1. Initialize without formatting (recommended if drive is already formatted)\n");
printf(" 2. Format with FAT32 filesystem\n");
printf(" 3. Format with ext4 filesystem\n");
printf("Enter choice (1-3): ");
char format_input[10];
if (!fgets(format_input, sizeof(format_input), stdin)) {
printf("Error: Failed to read input\n");
free(selected_drive);
return 1;
}
int format_choice = atoi(format_input);
int format_drive = (format_choice > 1);
const char* filesystem = "";
if (format_choice == 2) {
filesystem = "fat32";
} else if (format_choice == 3) {
filesystem = "ext4";
}
// Confirm operation
printf("\nConfirmation:\n");
printf("Drive: %s\n", selected_drive->mount_path);
printf("Name: %s\n", drive_name);
if (format_drive) {
printf("Format: YES (%s) - THIS WILL ERASE ALL DATA\n", filesystem);
} else {
printf("Format: NO (label only)\n");
}
printf("\nProceed? (y/N): ");
char confirm[10];
if (!fgets(confirm, sizeof(confirm), stdin) ||
(confirm[0] != 'y' && confirm[0] != 'Y')) {
printf("Operation cancelled.\n");
free(selected_drive);
return handle_usb_submenu();
}
// Initialize the drive
usb_operation_result_t result = initialize_usb_drive(
selected_drive->mount_path, drive_name, format_drive, filesystem);
if (result.success) {
printf("\n✓ USB drive initialized successfully!\n");
printf("Drive name: %s\n", drive_name);
printf("You can now copy pads to this drive.\n");
} else {
printf("\n✗ USB drive initialization failed: %s\n", result.error_message);
}
free(selected_drive);
printf("\nPress Enter to continue...");
getchar();
return handle_usb_submenu();
}
int handle_copy_pad_to_usb(void) {
printf("\n=== Copy Pad to USB Drive ===\n");
// Select pad to copy
char* selected_pad = select_pad_interactive("=== Select Pad to Copy ===",
"Select pad to copy to USB (by prefix)",
PAD_FILTER_ALL, 1);
if (!selected_pad) {
printf("Pad selection cancelled.\n");
return handle_usb_submenu();
}
// Select USB drive (OTP drives only)
usb_drive_info_t* selected_drive = select_usb_drive_interactive(
"Available OTP USB Drives", "Select destination USB drive", 1);
if (!selected_drive) {
printf("USB drive selection cancelled.\n");
free(selected_pad);
return handle_usb_submenu();
}
printf("\nCopying pad %.16s... to %s\n", selected_pad, selected_drive->volume_label);
// Copy the pad
int result = copy_pad_to_usb(selected_pad, selected_drive->mount_path);
if (result == 0) {
printf("✓ Pad successfully copied to USB drive!\n");
} else {
printf("✗ Failed to copy pad to USB drive (error code: %d)\n", result);
}
free(selected_pad);
free(selected_drive);
printf("\nPress Enter to continue...");
getchar();
return handle_usb_submenu();
}
int handle_import_pad_from_usb(void) {
printf("\n=== Import Pad from USB Drive ===\n");
// Select USB drive (OTP drives only)
usb_drive_info_t* selected_drive = select_usb_drive_interactive(
"Available OTP USB Drives", "Select source USB drive", 1);
if (!selected_drive) {
printf("USB drive selection cancelled.\n");
return handle_usb_submenu();
}
// List pads on the drive
char pad_list[100][65];
int pad_count = list_pads_on_drive(selected_drive->mount_path, pad_list, 100);
if (pad_count <= 0) {
printf("No pads found on USB drive %s\n", selected_drive->volume_label);
free(selected_drive);
printf("Press Enter to continue...");
getchar();
return handle_usb_submenu();
}
printf("\nPads available on %s:\n", selected_drive->volume_label);
printf("%-3s %-16s\n", "#", "Checksum");
printf("%-3s %-16s\n", "---", "----------------");
for (int i = 0; i < pad_count; i++) {
printf("%-3d %.16s...\n", i + 1, pad_list[i]);
}
printf("\nSelect pad number to import (1-%d) or 0 to cancel: ", pad_count);
char input[10];
if (!fgets(input, sizeof(input), stdin)) {
printf("Error: Failed to read input\n");
free(selected_drive);
return 1;
}
int selection = atoi(input);
if (selection < 1 || selection > pad_count) {
printf("Import cancelled.\n");
free(selected_drive);
return handle_usb_submenu();
}
char* selected_pad = pad_list[selection - 1];
printf("\nImporting pad %.16s... from %s\n", selected_pad, selected_drive->volume_label);
// Import the pad
int result = copy_pad_from_usb(selected_drive->mount_path, selected_pad);
if (result == 0) {
printf("✓ Pad successfully imported from USB drive!\n");
} else {
printf("✗ Failed to import pad from USB drive (error code: %d)\n", result);
}
free(selected_drive);
printf("\nPress Enter to continue...");
getchar();
return handle_usb_submenu();
}
int handle_duplicate_usb_drive(void) {
printf("\n=== Duplicate USB Drive ===\n");
printf("Copy selected pads from one OTP USB drive to another.\n\n");
// Select source drive
usb_drive_info_t* source_drive = select_usb_drive_interactive(
"Source OTP USB Drives", "Select source drive to copy from", 1);
if (!source_drive) {
printf("Source drive selection cancelled.\n");
return handle_usb_submenu();
}
// Select destination drive
usb_drive_info_t* dest_drive = select_usb_drive_interactive(
"Destination OTP USB Drives", "Select destination drive to copy to", 1);
if (!dest_drive) {
printf("Destination drive selection cancelled.\n");
free(source_drive);
return handle_usb_submenu();
}
// Prevent copying to same drive
if (strcmp(source_drive->mount_path, dest_drive->mount_path) == 0) {
printf("Error: Source and destination drives cannot be the same!\n");
free(source_drive);
free(dest_drive);
printf("Press Enter to continue...");
getchar();
return handle_usb_submenu();
}
// List pads on source drive
char pad_list[100][65];
int pad_count = list_pads_on_drive(source_drive->mount_path, pad_list, 100);
if (pad_count <= 0) {
printf("No pads found on source drive %s\n", source_drive->volume_label);
free(source_drive);
free(dest_drive);
printf("Press Enter to continue...");
getchar();
return handle_usb_submenu();
}
printf("\nSource: %s (%d pads)\n", source_drive->volume_label, pad_count);
printf("Destination: %s\n\n", dest_drive->volume_label);
// For simplicity, copy all pads (could be enhanced to allow selection)
printf("This will copy all %d pads from source to destination.\n", pad_count);
printf("Proceed? (y/N): ");
char confirm[10];
if (!fgets(confirm, sizeof(confirm), stdin) ||
(confirm[0] != 'y' && confirm[0] != 'Y')) {
printf("Duplication cancelled.\n");
free(source_drive);
free(dest_drive);
return handle_usb_submenu();
}
// Perform selective duplication with all pads
int result = duplicate_drive_selective(source_drive->mount_path,
dest_drive->mount_path, pad_list, pad_count);
if (result == 0) {
printf("\n✓ USB drive duplication completed successfully!\n");
} else {
printf("\n⚠ USB drive duplication completed with %d errors.\n", result);
}
free(source_drive);
free(dest_drive);
printf("\nPress Enter to continue...");
getchar();
return handle_usb_submenu();
}
int handle_verify_usb_drive(void) {
printf("\n=== Verify USB Drive Integrity ===\n");
// Select USB drive to verify
usb_drive_info_t* selected_drive = select_usb_drive_interactive(
"Available OTP USB Drives", "Select drive to verify", 1);
if (!selected_drive) {
printf("Drive selection cancelled.\n");
return handle_usb_submenu();
}
printf("\nVerifying integrity of drive: %s\n", selected_drive->volume_label);
printf("Mount path: %s\n\n", selected_drive->mount_path);
// List pads on the drive and verify each one
char pad_list[100][65];
int pad_count = list_pads_on_drive(selected_drive->mount_path, pad_list, 100);
if (pad_count <= 0) {
printf("No pads found on USB drive.\n");
free(selected_drive);
printf("Press Enter to continue...");
getchar();
return handle_usb_submenu();
}
printf("Found %d pads to verify:\n\n", pad_count);
int verified_count = 0;
int error_count = 0;
for (int i = 0; i < pad_count; i++) {
printf("[%d/%d] Verifying pad: %.16s...\n", i + 1, pad_count, pad_list[i]);
// Build pad path
char pad_path[1024];
snprintf(pad_path, sizeof(pad_path), "%s/%s.pad",
selected_drive->mount_path, pad_list[i]);
// Calculate and verify checksum
char calculated_checksum[65];
if (calculate_checksum(pad_path, calculated_checksum) == 0) {
if (strcmp(calculated_checksum, pad_list[i]) == 0) {
printf(" ✓ Integrity verified\n");
verified_count++;
} else {
printf(" ✗ Checksum mismatch!\n");
printf(" Expected: %.16s...\n", pad_list[i]);
printf(" Calculated: %.16s...\n", calculated_checksum);
error_count++;
}
} else {
printf(" ✗ Cannot calculate checksum\n");
error_count++;
}
}
printf("\nVerification completed:\n");
printf("Successfully verified: %d pads\n", verified_count);
printf("Errors found: %d pads\n", error_count);
if (error_count == 0) {
printf("✓ All pads on USB drive are intact!\n");
} else {
printf("⚠ Some pads have integrity issues. Consider re-copying them.\n");
}
free(selected_drive);
printf("\nPress Enter to continue...");
getchar();
return handle_usb_submenu();
}
int handle_list_usb_drives(void) {
printf("\n=== List USB Drives ===\n");
list_usb_drives_v2();
printf("\nPress Enter to continue...");
getchar();
return handle_usb_submenu();
}
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// USB COMMAND LINE INTERFACE HANDLERS
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
int handle_list_usb_drives_cli(void) {
list_usb_drives_v2();
return 0;
}
int handle_usb_init_cli(int argc, char* argv[]) {
const char* drive_name = argv[2];
const char* drive_path = (argc > 3) ? argv[3] : NULL;
// Validate drive name
if (strncmp(drive_name, "OTP_", 4) != 0) {
printf("Error: Drive name must start with 'OTP_'\n");
return 1;
}
if (strlen(drive_name) < 5 || strlen(drive_name) > 32) {
printf("Error: Drive name must be 5-32 characters\n");
return 1;
}
const char* mount_path;
usb_drive_info_t* selected_drive = NULL;
if (drive_path) {
// Direct path specified
mount_path = drive_path;
// Verify it's a valid mount point
if (access(drive_path, W_OK) != 0) {
printf("Error: Cannot access or write to specified path: %s\n", drive_path);
return 1;
}
} else {
// Interactive drive selection
printf("No drive path specified. Available USB drives:\n");
selected_drive = select_usb_drive_interactive(
"Available USB Drives", "Select drive to initialize", 0);
if (!selected_drive) {
printf("No drive selected.\n");
return 1;
}
mount_path = selected_drive->mount_path;
}
printf("Initializing USB drive...\n");
printf("Drive path: %s\n", mount_path);
printf("Drive name: %s\n", drive_name);
// Initialize without formatting by default in CLI mode
usb_operation_result_t result = initialize_usb_drive(mount_path, drive_name, 0, "");
if (result.success) {
printf("✓ USB drive initialized successfully!\n");
} else {
printf("✗ USB drive initialization failed: %s\n", result.error_message);
if (selected_drive) free(selected_drive);
return 1;
}
if (selected_drive) free(selected_drive);
return 0;
}
int handle_usb_copy_cli(const char* pad_prefix, const char* usb_path) {
// Find pad by prefix
char* pad_checksum = find_pad_by_prefix(pad_prefix);
if (!pad_checksum) {
printf("Error: No pad found matching prefix '%s'\n", pad_prefix);
return 1;
}
// Verify USB path is accessible
if (access(usb_path, W_OK) != 0) {
printf("Error: Cannot access USB drive path: %s\n", usb_path);
free(pad_checksum);
return 1;
}
printf("Copying pad %.16s... to %s\n", pad_checksum, usb_path);
int result = copy_pad_to_usb(pad_checksum, usb_path);
if (result == 0) {
printf("✓ Pad successfully copied to USB drive!\n");
} else {
printf("✗ Failed to copy pad to USB drive (error code: %d)\n", result);
free(pad_checksum);
return 1;
}
free(pad_checksum);
return 0;
}
int handle_usb_import_cli(const char* pad_checksum, const char* usb_path) {
// Verify USB path is accessible
if (access(usb_path, R_OK) != 0) {
printf("Error: Cannot access USB drive path: %s\n", usb_path);
return 1;
}
// Verify pad exists on USB
char pad_path[1024];
snprintf(pad_path, sizeof(pad_path), "%s/%s.pad", usb_path, pad_checksum);
if (access(pad_path, R_OK) != 0) {
printf("Error: Pad not found on USB drive: %s\n", pad_checksum);
return 1;
}
printf("Importing pad %.16s... from %s\n", pad_checksum, usb_path);
int result = copy_pad_from_usb(usb_path, pad_checksum);
if (result == 0) {
printf("✓ Pad successfully imported from USB drive!\n");
} else {
printf("✗ Failed to import pad from USB drive (error code: %d)\n", result);
return 1;
}
return 0;
}
int handle_usb_verify_cli(const char* usb_path) {
// Verify USB path is accessible
if (access(usb_path, R_OK) != 0) {
printf("Error: Cannot access USB drive path: %s\n", usb_path);
return 1;
}
printf("Verifying USB drive integrity: %s\n", usb_path);
// List pads on the drive
char pad_list[100][65];
int pad_count = list_pads_on_drive(usb_path, pad_list, 100);
if (pad_count <= 0) {
printf("No pads found on USB drive.\n");
return 1;
}
printf("Found %d pads to verify:\n", pad_count);
int verified_count = 0;
int error_count = 0;
for (int i = 0; i < pad_count; i++) {
printf("[%d/%d] Verifying pad: %.16s...", i + 1, pad_count, pad_list[i]);
// Build pad path
char pad_path[1024];
snprintf(pad_path, sizeof(pad_path), "%s/%s.pad", usb_path, pad_list[i]);
// Calculate and verify checksum
char calculated_checksum[65];
if (calculate_checksum(pad_path, calculated_checksum) == 0) {
if (strcmp(calculated_checksum, pad_list[i]) == 0) {
printf("\n");
verified_count++;
} else {
printf(" ✗ (checksum mismatch)\n");
error_count++;
}
} else {
printf(" ✗ (cannot calculate checksum)\n");
error_count++;
}
}
printf("\nVerification results:\n");
printf("Successfully verified: %d pads\n", verified_count);
printf("Errors found: %d pads\n", error_count);
return (error_count > 0) ? 1 : 0;
}
void print_usage(const char* program_name) {
printf("OTP Cipher - One Time Pad Implementation v0.3.7\n");
printf("Built for testing entropy system\n");
printf("Usage:\n");
printf(" %s - Interactive mode\n", program_name);
printf(" %s generate|-g <size> - Generate new pad\n", program_name);
printf(" %s encrypt|-e [pad_checksum_prefix] [text] - Encrypt text\n", program_name);
printf(" %s decrypt|-d [encrypted_message] - Decrypt message\n", program_name);
printf(" %s -f <file> <pad_prefix> [-a] [-o <out>] - Encrypt file\n", program_name);
printf(" %s list|-l - List available pads\n", program_name);
printf("\nFile Operations:\n");
printf(" -f <file> <pad> - Encrypt file (binary .otp format)\n");
printf(" -f <file> <pad> -a - Encrypt file (ASCII .otp.asc format)\n");
printf(" -o <output> - Specify output filename\n");
printf("\nShort flags:\n");
printf(" -g generate -e encrypt -d decrypt -l list -f file\n");
printf("\nExamples:\n");
printf(" %s -e 1a2b3c \"Hello world\" - Encrypt inline text\n", program_name);
printf(" %s -f document.pdf 1a2b - Encrypt file (binary)\n", program_name);
printf(" %s -f document.pdf 1a2b -a - Encrypt file (ASCII)\n", program_name);
printf(" %s -f document.pdf 1a2b -o secret.otp - Encrypt with custom output\n", program_name);
printf(" %s -d \"-----BEGIN OTP MESSAGE-----...\" - Decrypt message/file\n", program_name);
printf(" %s -d encrypted.otp.asc - Decrypt ASCII file\n", program_name);
printf(" %s -g 1GB - Generate 1GB pad\n", program_name);
printf(" %s -l - List pads\n", program_name);
printf("\nUSB Operations:\n");
printf(" usb-list - List all USB drives\n");
printf(" usb-init <name> [path] - Initialize USB drive for OTP\n");
printf(" usb-copy <pad> <drive_path> - Copy pad to USB drive\n");
printf(" usb-import <pad> <drive_path> - Import pad from USB drive\n");
printf(" usb-verify <drive_path> - Verify USB drive integrity\n");
printf("\nUSB Examples:\n");
printf(" %s usb-list - Show all USB drives\n", program_name);
printf(" %s usb-init OTP_BACKUP - Initialize drive (interactive)\n", program_name);
printf(" %s usb-init OTP_BACKUP /media/user/USB - Initialize specific drive\n", program_name);
printf(" %s usb-copy 1a2b3c /media/user/OTP_BACKUP - Copy pad to USB\n", program_name);
printf(" %s usb-import 1a2b3c4d /media/user/OTP_BACKUP - Import pad from USB\n", program_name);
printf(" %s usb-verify /media/user/OTP_BACKUP - Verify USB drive\n", program_name);
printf("\nSize examples: 1GB, 5TB, 512MB, 2048 (bytes)\n");
printf("Pad selection: Full chksum or prefix\n");
}