pit

add pit

fc02a1b4913123f56ecfb99672ff4c9c386687cf

IIIlllIIIllI <seb.michalk@gmail.com>

2025-04-05 18:37:22 +0000

 LICENSE   |   21 ++
 Makefile  |   29 ++
 config.mk |   18 +
 pit       |  Bin 0 -> 46432 bytes
 pit.c     | 1200 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 pit.o     |  Bin 0 -> 37016 bytes
 readme.md |  137 +++++++
 7 files changed, 1405 insertions(+)

diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..182afff
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 nyangkosense
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..ce7e028
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,29 @@
+# pit - encrypted container utility
+
+include config.mk
+
+SRC = pit.c
+OBJ = ${SRC:.c=.o}
+
+all: pit
+
+.c.o:
+	${CC} -c ${CFLAGS} $<
+
+${OBJ}: config.mk
+
+pit: ${OBJ}
+	${CC} -o $@ ${OBJ} ${LDFLAGS}
+
+clean:
+	rm -f pit ${OBJ}
+
+install: all
+	mkdir -p ${DESTDIR}${PREFIX}/bin
+	cp -f pit ${DESTDIR}${PREFIX}/bin
+	chmod 755 ${DESTDIR}${PREFIX}/bin/pit
+
+uninstall:
+	rm -f ${DESTDIR}${PREFIX}/bin/pit
+
+.PHONY: all clean install uninstall
\ No newline at end of file
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..e86b8a5
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,18 @@
+
+# pit version
+VERSION = 0.1
+
+# paths
+PREFIX = /usr/local
+
+# includes and libs
+INCS = -I/usr/include
+LIBS = -lcryptsetup -lsodium
+
+# flags
+CPPFLAGS = -DVERSION=\"${VERSION}\"
+CFLAGS = -std=c99 -pedantic -Wall -Wextra ${INCS} ${CPPFLAGS}
+LDFLAGS = ${LIBS}
+
+# compiler
+CC = cc
diff --git a/pit b/pit
new file mode 100755
index 0000000..84d1f21
Binary files /dev/null and b/pit differ
diff --git a/pit.c b/pit.c
new file mode 100644
index 0000000..e592ae1
--- /dev/null
+++ b/pit.c
@@ -0,0 +1,1200 @@
+/* pit 
+ * See LICENSE file for copyright and license details. */
+#define _POSIX_C_SOURCE 200809L
+#define _DEFAULT_SOURCE  
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libcryptsetup.h>
+#include <limits.h>
+#include <pwd.h>
+#include <signal.h>
+#include <sodium.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/mount.h>
+#include <termios.h>
+#include <unistd.h>
+
+/* arbitrary sizes */
+#define PIT_BLOCK_SIZE 4096
+#define KEY_SIZE 32    /* 256 bit */
+#define SALT_SIZE 32
+#define ITER_COUNT 500000
+
+#define VERSION "0.1"
+#define MAPPER_PREFIX "pit-"
+#define MOUNTPOINT_PREFIX "/mnt/pit-"
+#define FS_TYPE "ext4"
+
+/* cipher conf */
+#define CIPHER "aes"
+#define CIPHER_MODE "xts-plain64"
+#define HASH "sha256"
+
+/* types */
+typedef struct Pit {
+    char *path;
+    size_t size;
+    char *key;
+    int ismounted;
+} Pit;
+
+/* function declarations, kept the names more verbose here */
+static void die(const char *fmt, ...);
+static void usage(void);
+static int init_sec_mem(void);
+static void *secure_alloc(size_t size);
+static void secure_free(void *ptr, size_t size);
+static void term_cleanup(int signo);
+static int read_password(char *password, size_t size, const char *prompt);
+static int exec_cmd(const char *fmt, ...);
+static const char *get_username(void);
+static int check_sudo_tool(const char *tool);
+static int run_privileged(const char *fmt, ...);
+static int generate_key(const char *keyfile);
+static int read_key_file(const char *path, char **key);
+static int create_pit(const char *path, size_t size);
+static char *get_mapper_path(const char *path);
+static int cleanup_stale_device(const char *name);
+static int setup_device_mapper(const char *path, const char *key);
+static int teardown_device_mapper(const char *path);
+static int check_filesystem(const char *device);
+static int debug_fs_info(const char *device); /* this can be removed if not needed, just used for verbose printing */
+static int create_filesystem(const char *device);
+static int ensure_mount_dir(void);
+static int create_mount_point(const char *path);
+static int mount_filesystem(const char *device, const char *mountpoint);
+static int unmount_filesystem(const char *mountpoint);
+static int open_pit(const char *path, const char *keyfile);
+static int close_pit(const char *path);
+static int list_pits(void); /* this is not used */
+static int find_mounted_pits(char ***paths, int *count);
+static int panic_close(void);
+
+/* globals */
+static const char *program_name;
+static int term_modified = 0;
+static struct termios saved_term;
+static long pagesize;
+
+/* function implementations */
+static void
+die(const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start(ap, fmt);
+    vfprintf(stderr, fmt, ap);
+    va_end(ap);
+    exit(1);
+}
+
+static int
+init_sec_mem(void)
+{
+    struct rlimit rlim;
+    size_t required_mem = 8 * 1024 * 1024; /* 8mb minimum */
+
+    if (getrlimit(RLIMIT_MEMLOCK, &rlim) == 0) {
+        if (rlim.rlim_cur < required_mem) {
+
+            if (geteuid() == 0) {
+                rlim.rlim_cur = required_mem;
+                rlim.rlim_max = required_mem;
+                if (setrlimit(RLIMIT_MEMLOCK, &rlim) < 0){
+                    fprintf(stderr, "pit: warning: couldn't increase memory lock limit\n");
+                }
+            }
+        }
+    }
+
+    if (mlockall(MCL_CURRENT | MCL_FUTURE) < 0){
+        fprintf(stderr, "pit: warning: couldn't lock memory pages: %s\n",
+                strerror(errno));
+        fprintf(stderr, "pit: sensitive data might be swapped to disk\n");
+        return -1;
+    }
+
+    return 0;
+}
+
+static void *
+secure_alloc(size_t size)
+{
+    void *ptr; 
+
+    pagesize = sysconf(_SC_PAGESIZE);
+    if (pagesize < 0) {
+        fprintf(stderr, "pit: could not get system page size: %s\n",
+            strerror(errno));
+    }
+
+    if (posix_memalign(&ptr, pagesize, size) != 0) {
+        return NULL;
+    }
+
+    if (mlock(ptr, size) < 0) {
+        free(ptr);
+        return NULL;
+    }
+
+    if (madvise(ptr, size, MADV_DONTDUMP) < 0) {
+        munlock(ptr, size);
+        free(ptr);
+        return NULL;
+    }
+
+    return ptr;
+}
+
+static void
+secure_free(void *ptr, size_t size)
+{
+    if (ptr) {
+        sodium_memzero(ptr, size);
+        munlock(ptr, size);
+        free(ptr);
+    }
+}
+
+/* todo: format this better and more clearly */
+static void 
+usage(void)
+{
+    die("usage: pit [-v] [-h] command [arguments]\n"
+        "Commands:\n"
+        "  dig FILE 10                 - create new empty pit file of 10 MB size\n"
+        "  key KEY.key                 - generate new encrypted key file\n"
+        "  open FILE KEY.key           - open an existing pit\n"
+        "  close PATH                  - close an opened pit\n"
+        "  list                        - list opened pits\n"
+        "  panic                       - emergency close all pits (forced)\n"
+        "  example: pit dig container.pit\n"
+        "           pit key container.key\n"
+        "           pit open container.pit container.key\n");
+}
+
+static void
+term_cleanup(int signo)
+{
+    if (term_modified) {
+        tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_term);
+        fprintf(stderr, "\n");
+        term_modified = 0;
+    }
+    if (signo != 0) {
+        exit(1);
+    }
+}
+
+static int
+read_password(char *password, size_t size, const char *prompt)
+{
+    struct termios new;
+    struct sigaction sa;
+    int len;
+
+    sa.sa_handler = term_cleanup;
+    sigemptyset(&sa.sa_mask);
+    sa.sa_flags = 0;
+    sigaction(SIGINT, &sa, NULL);
+    sigaction(SIGTERM, &sa, NULL);
+
+    if (tcgetattr(STDIN_FILENO, &saved_term) != 0)
+        return -1;
+
+    new = saved_term;
+    new.c_lflag &= ~ECHO;  /* disable echo */
+
+    if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new) != 0)
+        return -1;
+
+    term_modified = 1;  
+
+    /* Show prompt and get password */
+    fprintf(stderr, "%s", prompt);
+    fflush(stderr);
+
+    if (!fgets(password, size, stdin)) {
+        term_cleanup(0);
+        return -1;
+    }
+
+    term_cleanup(0);
+
+    len = strlen(password);
+    if (len > 0 && password[len-1] == '\n')
+        password[--len] = '\0';
+
+    return len;
+}
+
+static int
+exec_cmd(const char *fmt, ...)
+{
+    char cmd[4096];
+    va_list ap;
+    int ret;
+
+    va_start(ap, fmt);
+    vsnprintf(cmd, sizeof(cmd), fmt, ap);
+    va_end(ap);
+
+    ret = system(cmd);
+    return WEXITSTATUS(ret);
+}
+
+static const char *
+get_username(void)
+{
+    uid_t uid = getuid();
+    struct passwd *pw = getpwuid(uid);
+    return pw ? pw->pw_name : NULL;
+}
+
+static int
+check_sudo_tool(const char *tool)
+{
+    char path[PATH_MAX];
+    snprintf(path, sizeof(path), "/usr/bin/%s", tool);
+    return access(path, X_OK) == 0;
+}
+
+static int
+run_privileged(const char *fmt, ...)
+{
+    char cmd[4096];
+    va_list ap;
+    const char *username;
+    const char *sudo_tool = NULL;
+
+    if (geteuid() == 0) {
+        va_start(ap, fmt);
+        vsnprintf(cmd, sizeof(cmd), fmt, ap);
+        va_end(ap);
+        return exec_cmd("%s", cmd);
+    }
+
+    if (check_sudo_tool("doas"))
+        sudo_tool = "doas";
+    else if (check_sudo_tool("sudo"))
+        sudo_tool = "sudo";
+
+    if (!sudo_tool) {
+        fprintf(stderr, "pit: no privilege escalation tool found\n");
+        return -1;
+    }
+
+    username = get_username();
+    if (!username) {
+        fprintf(stderr, "pit: cannot get username\n");
+        return -1;
+    }
+
+    va_start(ap, fmt);
+    vsnprintf(cmd, sizeof(cmd), fmt, ap);
+    va_end(ap);
+
+    if (strcmp(sudo_tool, "doas") == 0) {
+        return exec_cmd("doas %s", cmd);
+    } else {
+        return exec_cmd("sudo -p '[sudo] Enter password for user %s: ' %s",
+                       username, cmd);
+    }
+}
+
+static int
+generate_key(const char *keyfile)
+{
+    unsigned char *key = secure_alloc(KEY_SIZE);
+    unsigned char *salt = secure_alloc(SALT_SIZE);
+    unsigned char *encrypted = secure_alloc(KEY_SIZE + SALT_SIZE);
+    char *password = secure_alloc(1024);
+    char *verify = secure_alloc(1024);
+    int pwlen;
+    int ret = -1; /* def to error */
+    int pwhash_result = -1;
+
+    if (!key || !salt || !encrypted || !password || !verify) {
+        fprintf(stderr, "pit: failed to allocate secure memory\n");
+        goto cleanup;
+    }
+
+    if (sodium_init() < 0) {
+        fprintf(stderr, "pit: failed to initialize sodium\n");
+        goto cleanup;
+    }
+
+    randombytes_buf(key, KEY_SIZE);
+    randombytes_buf(salt, SALT_SIZE);
+
+    pwlen = read_password(password, 1024, "Enter password for key encryption (no echo): ");
+    if (pwlen <= 0) {
+        fprintf(stderr, "pit: failed to read password\n");
+        goto cleanup;
+    }
+
+    if (read_password(verify, 1024, "Verify password: ") <= 0) {
+        fprintf(stderr, "pit: failed to read password verification\n");
+        goto cleanup;
+    }
+
+    if (strcmp(password, verify) != 0) {
+        fprintf(stderr, "pit: passwords do not match\n");
+        goto cleanup;
+    }
+
+    /* Try with SENSITIVE settings first */
+    pwhash_result = crypto_pwhash(
+        encrypted + SALT_SIZE, KEY_SIZE,
+        password, pwlen,
+        salt,
+        crypto_pwhash_OPSLIMIT_SENSITIVE,
+        crypto_pwhash_MEMLIMIT_SENSITIVE,
+        crypto_pwhash_ALG_DEFAULT);
+
+    /* If SENSITIVE fails, try MODERATE */
+    if (pwhash_result != 0) {
+        fprintf(stderr, "pit: key derivation failed with sensitive memory settings - trying with moderate...\n");
+        pwhash_result = crypto_pwhash(
+            encrypted + SALT_SIZE, KEY_SIZE,
+            password, pwlen,
+            salt,
+            crypto_pwhash_OPSLIMIT_MODERATE,
+            crypto_pwhash_MEMLIMIT_MODERATE,
+            crypto_pwhash_ALG_DEFAULT);
+    }
+
+    /* If MODERATE fails, try MINIMAL */
+    if (pwhash_result != 0) {
+        fprintf(stderr, "pit: key derivation with moderate memory settings failed - trying minimal...\n");
+        pwhash_result = crypto_pwhash(
+            encrypted + SALT_SIZE, KEY_SIZE,
+            password, pwlen,
+            salt,
+            crypto_pwhash_OPSLIMIT_MIN,
+            crypto_pwhash_MEMLIMIT_MIN,
+            crypto_pwhash_ALG_DEFAULT);
+    }
+
+    /* If all attempts failed */
+    if (pwhash_result != 0) {
+        fprintf(stderr, "pit: key derivation failed - system has insufficient memory\n");
+        goto cleanup;
+    }
+
+    /* If we reached here, one of the pwhash attempts succeeded */
+    memcpy(encrypted, salt, SALT_SIZE);
+
+    FILE *f = fopen(keyfile, "wb");
+    if (!f) {
+        fprintf(stderr, "pit: cannot create key file: %s\n", strerror(errno));
+        goto cleanup;
+    }
+
+    if (fwrite(encrypted, 1, KEY_SIZE + SALT_SIZE, f) != KEY_SIZE + SALT_SIZE) {
+        fprintf(stderr, "pit: failed to write key file: %s\n", strerror(errno));
+        fclose(f);
+        goto cleanup;
+    }
+
+    fclose(f);
+    printf("pit: key generated successfully\n");
+    ret = 0; // success
+
+cleanup:
+    secure_free(key, KEY_SIZE);
+    secure_free(salt, SALT_SIZE);
+    secure_free(encrypted, KEY_SIZE + SALT_SIZE);
+    secure_free(password, 1024);
+    secure_free(verify, 1024);
+    return ret;
+}
+
+static int
+create_pit(const char *path, size_t size)
+{
+    int fd;
+    char *buf = secure_alloc(PIT_BLOCK_SIZE);
+    size_t remain;
+
+    if (!buf) {
+        die("pit: out of memory\n");
+    }
+
+    if (access(path, F_OK) == 0)
+        die("pit: %s already exists\n", path);
+
+    fd = open(path, O_WRONLY | O_CREAT, 0600);
+    if (fd < 0)
+        die("pit: cannot create %s: %s\n", path, strerror(errno));
+
+    randombytes_buf(buf, PIT_BLOCK_SIZE);
+
+    remain = size * 1024 * 1024; 
+    while (remain > 0) {
+        ssize_t nwrite = write(fd, buf, PIT_BLOCK_SIZE);
+        if (nwrite < 0) {
+            close(fd);
+            secure_free(buf, PIT_BLOCK_SIZE);
+            die("pit: write error: %s\n", strerror(errno));
+        }
+        remain -= nwrite;
+        
+        /* refresh random data every 50 blocks */
+        if (remain % (50 * PIT_BLOCK_SIZE) == 0)
+            randombytes_buf(buf, PIT_BLOCK_SIZE);
+    }
+
+    close(fd);
+    secure_free(buf, PIT_BLOCK_SIZE);
+    return 0;
+}
+
+static int
+read_key_file(const char *path, char **key)
+{
+    FILE *f;
+    unsigned char *encrypted = secure_alloc(KEY_SIZE + SALT_SIZE);
+    unsigned char *decrypted = secure_alloc(KEY_SIZE);
+    char *password = secure_alloc(1024);
+    int pwlen;
+    int ret = -1;  /* see above */
+
+    if (!encrypted || !decrypted || !password) {
+        fprintf(stderr, "pit: failed to allocate secure memory\n");
+        goto cleanup;
+    }
+
+    f = fopen(path, "r");
+    if (!f) {
+        die("pit: cannot open key file %s: %s\n", path, strerror(errno));
+    }
+
+    if (fread(encrypted, 1, KEY_SIZE + SALT_SIZE, f) != KEY_SIZE + SALT_SIZE) {
+        fclose(f);
+        die("pit: invalid key file size\n");
+    }
+    fclose(f);
+
+    fflush(stdout);
+    if (read_password(password, 1024, "Enter password for key (no echo): ") <= 0) {
+        fprintf(stderr, "pit: failed to read password\n");
+        goto cleanup;
+    }
+
+    pwlen = strlen(password);
+    if (pwlen > 0 && password[pwlen-1] == '\n')
+        password[--pwlen] = 0;
+
+    int r = crypto_pwhash(
+        decrypted, KEY_SIZE,
+        password, pwlen,
+        encrypted,
+        crypto_pwhash_OPSLIMIT_MIN,
+        crypto_pwhash_MEMLIMIT_MIN,
+        crypto_pwhash_ALG_DEFAULT);
+
+    if (r != 0) {
+        fprintf(stderr, "pit: trying with moderate memory settings ... \n");
+        r = crypto_pwhash(
+            decrypted, KEY_SIZE,
+            password, pwlen,
+            encrypted,
+            crypto_pwhash_OPSLIMIT_MODERATE,
+            crypto_pwhash_MEMLIMIT_MODERATE,
+            crypto_pwhash_ALG_DEFAULT);
+    }
+
+    if (r !=0) {
+        fprintf(stderr, "pit: trying with sensitive memory settings ...\n");
+        r = crypto_pwhash(
+            decrypted, KEY_SIZE,
+            password, pwlen,
+            encrypted,
+            crypto_pwhash_OPSLIMIT_SENSITIVE,
+            crypto_pwhash_MEMLIMIT_SENSITIVE,
+            crypto_pwhash_ALG_DEFAULT);
+    }
+    
+    if (r != 0) {
+        fprintf(stderr, "pit: key derivation failed - insufficient memory or wrong password\n");
+        goto cleanup;
+    }
+
+    *key = (char*)decrypted;
+    decrypted = NULL;
+    ret = 0; /* success, otherwise ret = -1 */
+
+cleanup:
+    secure_free(encrypted, KEY_SIZE + SALT_SIZE);
+    if (decrypted) secure_free(decrypted, KEY_SIZE);
+    secure_free(password, 1024);
+    return ret;
+}
+
+static char *
+get_mapper_path(const char *path)
+{
+    static char mapper[PATH_MAX];
+    const char *name = strrchr(path, '/');
+    
+    name = name ? name + 1 : path;
+    snprintf(mapper, sizeof(mapper), "/dev/mapper/%s%s", MAPPER_PREFIX, name);
+    return mapper;
+}
+
+static int
+cleanup_stale_device(const char *name)
+{
+    char path[PATH_MAX];
+    struct stat st;
+    const char *mapper_name;
+    
+    mapper_name = strrchr(get_mapper_path(name), '/');
+    if (!mapper_name)
+        return -1;
+    mapper_name++; 
+
+    snprintf(path, sizeof(path), "/dev/mapper/%s", mapper_name);
+    if (stat(path, &st) == 0) {
+        struct crypt_device *cd;
+        int r;
+
+        printf("pit: cleaning up stale device %s\n", mapper_name);
+        
+        r = crypt_init_by_name(&cd, mapper_name);
+        if (r < 0) {
+            fprintf(stderr, "pit: failed to init device %s\n", mapper_name);
+            return -1;
+        }
+
+        r = crypt_deactivate(cd, mapper_name);
+        crypt_free(cd);
+        
+        if (r < 0) {
+            fprintf(stderr, "pit: failed to deactivate stale device %s\n", mapper_name);
+            return -1;
+        }
+    }
+    return 0;
+}
+
+static int
+setup_device_mapper(const char *path, const char *key)
+{
+    struct crypt_device *cd;
+    int r;
+    const char *mapper_name;
+    const char *mapper_path;
+
+    mapper_path = get_mapper_path(path);
+    if (!mapper_path)
+        return -1;
+    mapper_name = strrchr(mapper_path, '/') + 1;
+
+    if (cleanup_stale_device(path) < 0){
+        fprintf(stderr, "pit: failed to clean up stale device\n");
+        return -1;
+    }
+
+    r = crypt_init(&cd, path);
+    if (r < 0) {
+        fprintf(stderr, "pit: crypt_init() failed for %s\n", path);
+        return r;
+    }
+
+    r = crypt_load(cd, CRYPT_LUKS1, NULL);
+    if (r == 0) {
+   
+        r = crypt_activate_by_passphrase(cd, mapper_name, 
+                                       CRYPT_ANY_SLOT,
+                                       key, KEY_SIZE, 0);
+        if (r < 0) {
+            fprintf(stderr, "pit: failed to activate device %s\n", path);
+            crypt_free(cd);
+            return r;
+        }
+    } else {
+
+        struct crypt_params_luks1 params = {
+            .hash = HASH,
+            .data_alignment = 0,
+            .data_device = NULL
+        };
+
+        r = crypt_format(cd, CRYPT_LUKS1, CIPHER, CIPHER_MODE,
+                        NULL, key, KEY_SIZE, &params);
+        if (r < 0) {
+            fprintf(stderr, "pit: failed to format device %s\n", path);
+            crypt_free(cd);
+            return r;
+        }
+
+        r = crypt_keyslot_add_by_volume_key(cd, 0, NULL, 0,
+                                           key, KEY_SIZE);
+        if (r < 0) {
+            fprintf(stderr, "pit: failed to add keyslot\n");
+            crypt_free(cd);
+            return r;
+        }
+
+        r = crypt_activate_by_passphrase(cd, mapper_name,
+                                       CRYPT_ANY_SLOT,
+                                       key, KEY_SIZE, 0);
+        if (r < 0) {
+            fprintf(stderr, "pit: failed to activate device %s\n", path);
+            crypt_free(cd);
+            return r;
+        }
+    }
+
+    crypt_free(cd);
+    return 0;
+}
+
+static int
+teardown_device_mapper(const char *path)
+{
+    struct crypt_device *cd;
+    int r;
+    const char *mapper_path;
+
+    mapper_path = get_mapper_path(path);
+    if (!mapper_path)
+        return -1;
+
+    r = crypt_init_by_name(&cd, strrchr(mapper_path, '/') + 1);
+    if (r < 0) {
+        fprintf(stderr, "pit: crypt_init_by_name() failed for %s\n", mapper_path);
+        return r;
+    }
+
+    r = crypt_deactivate(cd, strrchr(mapper_path, '/') + 1);
+    if (r < 0) {
+        fprintf(stderr, "pit: failed to deactivate device %s\n", mapper_path);
+        crypt_free(cd);
+        return r;
+    }
+
+    crypt_free(cd);
+    return 0;
+}
+
+static int
+check_filesystem(const char *device)
+{
+    int fd;
+    unsigned char buf[2048];  /* ext4 superblock starts at offset 1024 */
+    ssize_t bytes_read;
+
+    fd = open(device, O_RDONLY);
+    if (fd < 0)
+        return 0;
+
+    if (lseek(fd, 1024, SEEK_SET) != 1024) {
+        close(fd);
+        return 0;
+    }
+
+    bytes_read = read(fd, buf, sizeof(buf));
+    close(fd);
+
+    if (bytes_read < 2)
+        return 0;
+
+    /* check ext4 superblock magic number at offset 0x38 (56) */
+    return (buf[0x38] == 0x53 && buf[0x39] == 0xEF);
+}
+
+static int
+debug_fs_info(const char *device)
+{
+    pid_t pid;
+    int status;
+
+    pid = fork();
+    if (pid < 0) {
+        return -1;
+    }
+
+    if (pid == 0) {
+        execl("/sbin/tune2fs", "tune2fs", "-l", device, NULL);
+        _exit(1);
+    }
+
+    waitpid(pid, &status, 0);
+    return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
+}
+
+static int
+open_pit(const char *path, const char *keyfile)
+{
+    char *key;
+    struct stat st;
+    int r;
+    char mount_dir[PATH_MAX];
+    const char *name;
+    const char *mapper_path;
+
+    if (stat(path, &st) < 0)
+        die("pit: cannot stat %s: %s\n", path, strerror(errno));
+
+    if (!S_ISREG(st.st_mode))
+        die("pit: %s is not a regular file\n", path);
+
+    if (read_key_file(keyfile, &key) < 0)
+        return -1;
+
+    r = setup_device_mapper(path, key);
+    free(key);
+
+    if (r < 0)
+        die("pit: failed to setup device mapper\n");
+
+    mapper_path = get_mapper_path(path);
+    if (!mapper_path)
+        die("pit: failed to get mapper path\n");
+
+    /* only create filesystem if one doesn't exist, otherwise it erases the data */
+    printf("pit: checking fs on %s\n", mapper_path);
+    if (!check_filesystem(mapper_path)) {
+        printf("pit: no filesystem detected, creating new one\n");
+        if (create_filesystem(mapper_path) < 0) {
+            teardown_device_mapper(path);
+            die("pit: failed to create filesystem\n");
+        }
+    } else {
+        printf("pit: existing filesystem found\n");
+        //debug_fs_info(mapper_path);
+    }
+
+    /* Create mount point */
+    if (create_mount_point(path) < 0) {
+        teardown_device_mapper(path);
+        die("pit: failed to create mount point\n");
+    }
+
+    name = strrchr(path, '/');
+    name = name ? name + 1 : path;
+    snprintf(mount_dir, sizeof(mount_dir), "%s%s", MOUNTPOINT_PREFIX, name);
+
+    if (mount_filesystem(mapper_path, mount_dir) < 0) {
+        teardown_device_mapper(path);
+        rmdir(mount_dir);
+        die("pit: failed to mount filesystem\n");
+    }
+
+    printf("pit: successfully opened %s on %s\n", path, mount_dir);
+    return 0;
+}
+
+/* todo .. */
+static int
+list_pits(void)
+{
+    char **mounted_paths = NULL;
+    int count = 0;
+    int ret = 0;
+    int i = 0;
+    
+    if (find_mounted_pits(&mounted_paths, &count) < 0) {
+        fprintf(stderr, "pit: failed to find mounted pits\n");
+        return -1;
+    }
+    
+    if (count == 0) {
+        printf("pit: no pits currently mounted\n");
+    } else {
+        printf("pit: %d pit%s currently mounted:\n", count, count == 1 ? "" : "s");
+        
+        for (i = 0; i < count; i++) {
+            if (mounted_paths[i]) {
+                char device_path[PATH_MAX] = {0};
+                FILE *mtab = fopen("/proc/mounts", "r");
+                if (mtab) {
+                    char line[PATH_MAX];
+                    while (fgets(line, sizeof(line), mtab)) {
+                        char mnt_path[PATH_MAX] = {0};
+                        char dev_path[PATH_MAX] = {0};
+                        sscanf(line, "%s %s", dev_path, mnt_path);
+                        
+                        if (strcmp(mnt_path, mounted_paths[i]) == 0) {
+                            strncpy(device_path, dev_path, PATH_MAX - 1);
+                            break;
+                        }
+                    }
+                    fclose(mtab);
+                }
+                
+                if (device_path[0]) {
+                    const char *mapper_prefix = "/dev/mapper/pit-";
+                    if (strncmp(device_path, mapper_prefix, strlen(mapper_prefix)) == 0) {
+                        printf("  %s -> %s\n", device_path + strlen(mapper_prefix), mounted_paths[i]);
+                    } else {
+                        printf("  %s -> %s\n", device_path, mounted_paths[i]);
+                    }
+                } else {
+                    printf("  %s\n", mounted_paths[i]);
+                }
+                
+                free(mounted_paths[i]);
+            }
+        }
+    }
+    
+    free(mounted_paths);
+    return ret;
+}
+
+static int
+ensure_mount_dir(void)
+{
+    struct stat st;
+
+    if (stat("/mnt", &st) < 0) {
+        if (mkdir("/mnt", 0755) < 0) {
+            fprintf(stderr, "pit: cannot create /mnt: %s\n", strerror(errno));
+            return -1;
+        }
+    }
+    return 0;
+}
+
+static int
+create_mount_point(const char *path)
+{
+    char mount_dir[PATH_MAX];
+    const char *name;
+
+    if (ensure_mount_dir() < 0)
+        return -1;
+
+    name = strrchr(path, '/');
+    name = name ? name + 1 : path;
+
+    snprintf(mount_dir, sizeof(mount_dir), "%s%s", MOUNTPOINT_PREFIX, name);
+
+    if (mkdir(mount_dir, 0700) < 0 && errno != EEXIST) {
+        fprintf(stderr, "pit: cannot create mountpoint %s: %s\n",
+                mount_dir, strerror(errno));
+        return -1;
+    }
+
+    return 0;
+}
+
+static int
+create_filesystem(const char *device)
+{
+    pid_t pid;
+    int status;
+
+    pid = fork();
+    if (pid < 0) {
+        fprintf(stderr, "pit: fork failed: %s\n", strerror(errno));
+        return -1;
+    }
+
+    if (pid == 0) {
+        
+        execl("/sbin/mkfs.ext4", "mkfs.ext4", "-q", "-F", device, NULL);
+        fprintf(stderr, "pit: exec mkfs.ext4 failed: %s\n", strerror(errno));
+        _exit(1);
+    }
+
+    if (waitpid(pid, &status, 0) < 0) {
+        fprintf(stderr, "pit: waitpid failed: %s\n", strerror(errno));
+        return -1;
+    }
+
+    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+        fprintf(stderr, "pit: mkfs.ext4 failed\n");
+        return -1;
+    }
+
+    return 0;
+}
+
+static int
+mount_filesystem(const char *device, const char *mountpoint)
+{
+    unsigned long flags = 0;
+    if (mount(device, mountpoint, FS_TYPE, flags, NULL) < 0) {
+        fprintf(stderr, "pit: mount failed: %s\n", strerror(errno));
+        return -1;
+    }
+    return 0;
+}
+
+static int
+unmount_filesystem(const char *mountpoint)
+{
+    if (umount(mountpoint) < 0) {
+        fprintf(stderr, "pit: umount failed: %s\n", strerror(errno));
+        return -1;
+    }
+    if (rmdir(mountpoint) < 0) {
+        fprintf(stderr, "pit: rmdir failed: %s\n", strerror(errno));
+        return -1;
+    }
+    return 0;
+}
+
+static int
+close_pit(const char *path)
+{
+    struct stat st;
+    int r;
+    char mount_dir[PATH_MAX];
+    const char *name;
+
+    if (stat(path, &st) < 0)
+        die("pit: cannot stat %s: %s\n", path, strerror(errno));
+
+    if (!S_ISREG(st.st_mode))
+        die("pit: %s is not a regular file\n", path);
+
+    /* Unmount filesystem */
+    name = strrchr(path, '/');
+    name = name ? name + 1 : path;
+    snprintf(mount_dir, sizeof(mount_dir), "%s%s", MOUNTPOINT_PREFIX, name);
+
+    if (unmount_filesystem(mount_dir) < 0)
+        die("pit: failed to unmount filesystem\n");
+
+    /* Teardown device mapper */
+    r = teardown_device_mapper(path);
+    if (r < 0)
+        die("pit: failed to teardown device mapper\n");
+
+    printf("pit: successfully closed %s\n", path);
+    return 0;
+}
+
+/* this is a bit hacky, might need improvements, for now it works */
+static int
+find_mounted_pits(char ***paths, int *count)
+{
+    FILE *mtab;
+    char line[PATH_MAX];
+    char **list = NULL;
+    int n = 0;
+    
+    mtab = fopen("/proc/mounts", "r");
+    if (!mtab) {
+        fprintf(stderr, "pit: cannot open /proc/mounts\n");
+        return -1;
+    }
+
+    while (fgets(line, sizeof(line), mtab)) {
+        if (strstr(line, MOUNTPOINT_PREFIX)) {
+            char *space = strchr(line, ' ');
+            if (!space)
+                continue;
+            
+            char **new_list = realloc(list, (n + 1) * sizeof(char *));
+            if (!new_list) {
+                fprintf(stderr, "pit: out of memory\n");
+                fclose(mtab);
+                for (int i = 0; i < n; i++)
+                    free(list[i]);
+                free(list);
+                return -1;
+            }
+            list = new_list;
+
+            *space = '\0';
+            space++;
+            char *mountpoint = space;
+            space = strchr(mountpoint, ' ');
+            if (!space)
+                continue;
+            *space = '\0';
+
+            list[n] = strdup(mountpoint);
+            if (!list[n]) {
+                fprintf(stderr, "pit: out of memory\n");
+                fclose(mtab);
+                for (int i = 0; i < n; i++)
+                    free(list[i]);
+                free(list);
+                return -1;
+            }
+            n++;
+        }
+    }
+
+    fclose(mtab);
+    *paths = list;
+    *count = n;
+    return 0;
+}
+
+/* panic_close forcefully, without any save attempt, closes the mounts by pit */
+static int
+panic_close(void)
+{
+    DIR *dir;
+    struct dirent *dp;
+    int ret = 0;
+    char **mounted = NULL;
+    int count = 0;
+
+    if (geteuid() != 0) {
+        return run_privileged("%s panic", program_name);
+    }
+
+    /* first find our mounted pits */
+    if (find_mounted_pits(&mounted, &count) == 0 && count > 0) {
+        printf("pit: force closing %d containers...\n", count);
+        
+        for (int i = 0; i < count; i++) {
+            if (!mounted[i]) continue;
+
+            /* check /proc for processes using this mount */
+            DIR *proc_dir = opendir("/proc");
+            if (proc_dir) {
+                struct dirent *pid_dir;
+                while ((pid_dir = readdir(proc_dir)) != NULL) {
+                    if (!isdigit(pid_dir->d_name[0])) 
+                        continue;
+
+                    char path[PATH_MAX], link[PATH_MAX];
+                    snprintf(path, sizeof(path), "/proc/%s/cwd", pid_dir->d_name);
+                    
+                    ssize_t len = readlink(path, link, sizeof(link) - 1);
+                    if (len > 0) {
+                        link[len] = '\0';
+                        if (strstr(link, mounted[i])) {
+                            pid_t pid = atoi(pid_dir->d_name);
+                            printf("pit: killing process %d using %s\n", pid, mounted[i]);
+                            kill(pid, SIGKILL);
+                        }
+                    }
+                }
+                closedir(proc_dir);
+                usleep(100000); /* small delay for processes to die */
+            }
+
+            /* Force unmount */
+            printf("pit: force unmounting %s\n", mounted[i]);
+            if (umount2(mounted[i], MNT_FORCE | MNT_DETACH) < 0) {
+                fprintf(stderr, "pit: cannot unmount %s: %s\n", 
+                        mounted[i], strerror(errno));
+            }
+            rmdir(mounted[i]);
+            free(mounted[i]);
+        }
+        free(mounted);
+    }
+
+    /* force close all pit mappers */
+    dir = opendir("/dev/mapper");
+    if (!dir) {
+        fprintf(stderr, "pit: cannot open /dev/mapper: %s\n", strerror(errno));
+        return -1;
+    }
+
+    while ((dp = readdir(dir)) != NULL) {
+        if (strncmp(dp->d_name, MAPPER_PREFIX, strlen(MAPPER_PREFIX)) == 0) {
+            printf("pit: force closing device %s\n", dp->d_name);
+            struct crypt_device *cd;
+            if (crypt_init_by_name(&cd, dp->d_name) == 0) {
+                crypt_deactivate_by_name(cd, dp->d_name, CRYPT_DEACTIVATE_FORCE);
+                crypt_free(cd);
+            }
+        }
+    }
+    closedir(dir);
+
+    return ret;
+}
+
+int
+main(int argc, char *argv[])
+{
+    program_name = argv[0];
+
+    if (argc < 2)
+        usage();
+
+    init_sec_mem();
+
+    if (!strcmp(argv[1], "-v")) {
+        printf("pit-%s\n", VERSION);
+        return 0;
+    }
+    if (!strcmp(argv[1], "-h"))
+        usage();
+
+    if (sodium_init() < 0)
+        die("pit: cannot initialize sodium\n");
+
+    if (!strcmp(argv[1], "key")) {
+        if (argc != 3)  
+            usage();
+        if (generate_key(argv[2]) < 0)
+            die("pit: cannot generate key\n");
+        return 0;
+    }
+
+    if (!strcmp(argv[1], "dig") || 
+        !strcmp(argv[1], "open") ||
+        !strcmp(argv[1], "close") ||
+        !strcmp(argv[1], "panic")) {
+        
+        if (geteuid() != 0) {
+
+            char cmd[4096] = {0};
+            int i;
+            
+            strcat(cmd, program_name);
+            for (i = 1; i < argc; i++) {
+                strcat(cmd, " ");
+                strcat(cmd, argv[i]);
+            }
+            
+            return run_privileged("%s", cmd);
+        }
+    }
+
+    if (!strcmp(argv[1], "dig")) {
+        if (argc != 4)
+            usage();
+        return create_pit(argv[2], atoi(argv[3]));
+    }
+
+    if (!strcmp(argv[1], "open")) {
+        if (argc != 4)
+            usage();
+        return open_pit(argv[2], argv[3]);
+    }
+    if (!strcmp(argv[1], "close")) {
+        if (argc != 3)
+            usage();
+        return close_pit(argv[2]);
+    }
+    if (!strcmp(argv[1], "list")) {
+        if (argc != 2)
+            usage();
+        return list_pits(); /* this is not implemented as of now */
+    }
+    if (!strcmp(argv[1], "panic")) {
+        if (argc != 2)
+            usage();
+        return panic_close();
+    }
+
+    usage();
+    return 1;
+}
diff --git a/pit.o b/pit.o
new file mode 100644
index 0000000..61a42f9
Binary files /dev/null and b/pit.o differ
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..4ca0a01
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,137 @@
+# pit - create and manage encrypted containers
+
+Need a simple way to keep your data private? `pit` is a minimalist encrypted container manager written in C.
+Create encrypted containers, toss your files in, and lock them up. It's like `tomb` but way more minimal - about ~1000 lines of C code.
+
+## Why would you want this?
+
+Ever needed to:
+
+- Store your private keys and passwords securely?
+- Have a secure place for your none of your business stuff?
+- Just want a simple, trustworthy way to encrypt files?
+
+`pit` gives you encrypted containers without the complexity. No fancy features, no bloat, no Qt and the like - just secure storage that you can actually understand and audit. If you're the kind of person who appreciates simple, well-written tools and cares about security without going full paranoid, this might be for you.
+
+Think of it as a minimal, no-nonsense approach to file encryption. It won't protect you from three-letter agencies, as that might require hw locked keys etc. but it'll keep your sensitive files secure from most threats while being simple enough that you can read and understand the entire code.
+
+## Overview
+
+- Written in C (~ 1000 LOC)
+- Simple, auditable codebase
+- Inspired by tomb, but a more minimal approach
+- No shell scripts
+- An attempt was made to not suck
+
+## Features
+
+- Single file containers with LUKS encryption
+- Password protected key files using libsodium
+- Protected memory handling
+- Random data container creation
+- Emergency panic close feature
+- Root escalation handling (sudo/doas)
+
+## Security
+
+### What pit is:
+- A simple and auditable encrypted container manager
+- Secure storage for sensitive files
+- Based on proven cryptographic standards (LUKS, AES-XTS, libsodium)
+- Protected against memory attacks
+
+### What pit is not:
+- Military grade encryption solution
+- Plausible deniability storage
+- Multi-user security system
+- Steganography tool
+
+### Security Features
+- LUKS containers with AES-XTS-PLAIN64 cipher
+- Password-based key derivation using Argon2 (via libsodium)
+- Secure memory handling (locked pages, memory wiping)
+- Hidden password input
+- Random data container creation
+- Emergency container closing ```pit panic``` (panic mode)
+
+### Security Limitations
+- No swap space verification
+- No hardware security module support
+- No key backup mechanism
+- No filesystem integrity verification
+
+## Usage
+
+```bash
+# Create a new 100MB container and key
+pit dig container.pit 100
+pit key container.key
+
+# Open container with key (requires root)
+pit open container.pit container.key
+
+# Close container
+pit close container.pit
+
+# Emergency close all containers
+pit panic
+```
+
+## Requirements
+- libsodium
+- libcryptsetup
+- sudo or doas
+
+#### Debian/Ubuntu
+```sudo apt update && sudo apt install libsodium-dev libcryptsetup-dev```
+
+#### Red Hat/CentOS/Fedora
+```sudo dnf install libsodium-devel cryptsetup-devel```
+
+#### Arch Linux
+```sudo pacman -S libsodium cryptsetup```
+
+#### Gentoo
+```sudo emerge libsodium cryptsetup```
+
+#### Alpine Linux
+```sudo apk add libsodium-dev cryptsetup-dev```
+
+## Security Considerations
+
+1. your system should have:
+   - Encrypted swap or no swap
+   - Full disk encryption
+   - Protected boot process
+
+2. pit doesn't protect against:
+   - Physical memory attacks
+   - Evil maid attacks
+   - Hardware keyloggers
+
+3. key files are protected but:
+   - Eventually need secure storage
+   - No backup mechanism
+   - No recovery option if lost
+
+## Comparison to other tools
+- Pure C implementation vs shell scripts
+- More minimal (~1000 LOC vs ~4000 or more, no GUI dependencies)
+- Fewer features but more auditable
+- No steganography or advanced features
+- Focus on core container operations
+
+## Installation
+```bash
+make
+sudo make install
+```
+
+## To do
+- Make pit for the truly paranoid 
+- Add swap space verification
+- Perhaps add backing up keys
+- Fs integrity checks
+
+## License
+MIT