From 998b06d89b869b9587830ec767c6e30f851fff29 Mon Sep 17 00:00:00 2001
From: lamp
Date: Sun, 5 Mar 2023 21:48:37 +0000
Subject: init

---
 README.md    |  18 ++++++
 common.h     |  40 ++++++++++++++
 pass-cache.c |  51 +++++++++++++++++
 passcached.c | 175 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 284 insertions(+)
 create mode 100644 README.md
 create mode 100644 common.h
 create mode 100644 pass-cache.c
 create mode 100644 passcached.c

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..11784bc
--- /dev/null
+++ b/README.md
@@ -0,0 +1,18 @@
+# pass-cache
+
+A simple daemon that caches the response from `pass my_entry`.
+For use with software that makes frequent calls to `pass`.
+
+## build
+
+```
+gcc passcached.c -o passcached
+gcc pass-cache.c -o pass-cache
+```
+
+## usage
+
+Start `passcached` in the background.
+Use `pass-cache` instead of `pass` to retrieve a secret.
+You will only need to unlock your keyring for the first invocation of `pass-cache` for a given entry; subsequent requests are served from the cache.
+The socket used for communication defaults to `$XDG_RUNTIME_DIR/pass-cache.sock`; this can be manually set with `PASS_CACHE_SOCK`.
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..28b5242
--- /dev/null
+++ b/common.h
@@ -0,0 +1,40 @@
+#ifndef _COMMON_H_
+#define _COMMON_H_
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+
+#define BUF_SIZE 512
+#define DEFAULT_SOCK_FNAME "/pass-cache.sock"
+
+bool socket_path_did_alloc = false;
+
+void die(char* message) {
+  fprintf(stderr, "%s\n", message);
+  exit(EXIT_FAILURE);
+}
+
+char* get_socket_path(void) {
+  char* explicit_path = getenv("PASS_CACHE_SOCK");
+  if (explicit_path) {
+    return explicit_path;
+  }
+  char* base_path = getenv("XDG_RUNTIME_DIR");
+  if (!base_path) {
+    base_path = "/tmp";
+  }
+  char* final_path = (char*)malloc(strlen(base_path) + strlen(DEFAULT_SOCK_FNAME));
+  socket_path_did_alloc = true;
+  strcpy(final_path, base_path);
+  strcat(final_path, DEFAULT_SOCK_FNAME);
+  return final_path;
+}
+
+void free_socket_path(char* path) {
+  if (socket_path_did_alloc) {
+    free(path);
+  }
+}
+
+#endif //ifndef _COMMON_H_
diff --git a/pass-cache.c b/pass-cache.c
new file mode 100644
index 0000000..1ba38a2
--- /dev/null
+++ b/pass-cache.c
@@ -0,0 +1,51 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "common.h"
+
+void usage(char* argv[]) {
+  printf("usage: %s ENTRY\n", argv[0]);
+  exit(1);
+}
+
+int main(int argc, char *argv[]) {
+  if (argc != 2) {
+    usage(argv);
+  }
+  struct sockaddr_un addr;
+
+  int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
+  if (sfd == -1) {
+    die("socket() failed!");
+  }
+
+  char* spath = get_socket_path();
+  memset(&addr, 0, sizeof(struct sockaddr_un));
+  addr.sun_family = AF_UNIX;
+  strncpy(addr.sun_path, spath, sizeof(addr.sun_path) - 1);
+
+  if (connect(sfd, (struct sockaddr *) &addr,
+              sizeof(struct sockaddr_un)) == -1) {
+    die("connect() failed!");
+  }
+  
+  if (write(sfd, argv[1], strlen(argv[1]) + 1) != strlen(argv[1]) + 1) {
+    die("write() failed!");
+  }
+
+  size_t num_read = 0;
+  char buf[BUF_SIZE];
+  while ((num_read = read(sfd, buf, BUF_SIZE)) > 0) {
+    if (write(STDOUT_FILENO, buf, num_read) != num_read) {
+      die("write() failed!");
+    }
+  }
+
+  free_socket_path(spath);
+  exit(EXIT_SUCCESS);
+}
diff --git a/passcached.c b/passcached.c
new file mode 100644
index 0000000..4ac7ba4
--- /dev/null
+++ b/passcached.c
@@ -0,0 +1,175 @@
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+
+#include "common.h"
+
+#define BACKLOG 5
+
+struct CacheEntry {
+  struct CacheEntry* next;
+  char* entry_name;
+  char* secret;
+};
+
+struct CacheEntry cache;
+
+volatile sig_atomic_t done = 0;
+
+void term(int signum) {
+  done = 1;
+}
+
+char* pass_secret_for_actual(char* entry_name, int* exit_code) {
+  int link[2];
+  pid_t pid;
+  if (pipe(link) == -1) {
+    die("pipe() failed!");
+  }
+  if ((pid = fork()) == -1) {
+    die("fork() failed!");
+  }
+  if (pid == 0) {
+    dup2 (link[1], STDOUT_FILENO);
+    close(link[0]);
+    close(link[1]);
+    execlp("pass", "pass", entry_name, NULL);
+    die("execlp() failed!");
+  } else {
+    if (close(link[1]) == -1) {
+      fprintf(stderr, "%s\n", "close() failed!");
+    }
+    char* final = malloc(BUF_SIZE);
+    if (!final) {
+      die("malloc() failed!");
+    }
+    char* buf = malloc(BUF_SIZE);
+    int nbytes = 0;
+    while(0 != (nbytes = read(link[0], buf, sizeof(buf)))) {
+      buf[nbytes] = 0;
+      if (strlen(final) + strlen(buf) < BUF_SIZE) {
+        strcat(final, buf);
+      }
+    }
+    if (waitpid(pid, exit_code, 0) == -1) {
+      fprintf(stderr, "%s\n", "waitpid() failed!");
+      return NULL;
+    } else {
+      return final;
+    }
+  }
+  // TODO
+  *exit_code = 0;
+  return "test";
+}
+
+char* pass_secret_for(char* entry_name, int* exit_code) {
+  struct CacheEntry* current = &cache;
+  while (current->next) {
+    if (!strcmp(entry_name, current->next->entry_name)) {
+      // cache hit
+      *exit_code = 0;
+      return current->next->secret;
+    }
+    current = current->next;
+  }
+  // cache miss
+  char* secret = pass_secret_for_actual(entry_name, exit_code);
+  if (secret && WIFEXITED(*exit_code) && (WEXITSTATUS(*exit_code) == 0)) {
+    struct CacheEntry* new_entry = malloc(sizeof(struct CacheEntry));
+    if (!new_entry) {
+      die("malloc() failed!");
+    }
+    new_entry->next = NULL;
+    char* new_entry_name = malloc(strlen(entry_name) + 1);
+    if (!new_entry_name) {
+      die("malloc() failed!");
+    }
+    strcpy(new_entry_name, entry_name);
+    new_entry->entry_name = new_entry_name;
+    new_entry->secret = secret;
+    current->next = new_entry;
+    return new_entry->secret;
+  } else {
+    return secret;
+  }
+}
+
+void destroy_cache(void) {
+  struct CacheEntry* current = cache.next;
+  while (current) {
+    struct CacheEntry* next = current->next;
+    free(current->entry_name);
+    free(current->secret);
+    free(current);
+    current = next;
+  }
+}
+
+int main(int argc, char *argv[]) {
+  struct sigaction action;
+  memset(&action, 0, sizeof(struct sigaction));
+  action.sa_handler = term;
+  sigaction(SIGTERM, &action, NULL);
+  struct sockaddr_un addr;
+
+  int sfd = socket(AF_UNIX, SOCK_STREAM, 0);
+  if (sfd == -1) {
+    die("socket() failed!");
+  }
+
+  char* spath = get_socket_path();
+  if (strlen(spath) > sizeof(addr.sun_path) - 1) {
+    die("Server socket path too long!");
+  }
+
+  if (remove(spath) == -1 && errno != ENOENT) {
+    die("remove() failed!");
+  }
+
+  memset(&addr, 0, sizeof(struct sockaddr_un));
+  addr.sun_family = AF_UNIX;
+  strncpy(addr.sun_path, spath, sizeof(addr.sun_path) - 1);
+
+  if (bind(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) == -1) {
+    die("bind() failed!");
+  }
+
+  if (listen(sfd, BACKLOG) == -1) {
+    die("listen() failed!");
+  }
+
+  printf("Listening at: %s\n", spath);
+
+  cache.next = NULL;
+  cache.entry_name = NULL;
+  cache.secret = NULL;
+  char buf[BUF_SIZE];
+  while (!done) {
+    int cfd = accept(sfd, NULL, NULL);
+    printf("Accepted socket fd = %d\n", cfd);
+    if (read(cfd, buf, BUF_SIZE) > 0) {
+      int exit_code = 0;
+      char* secret = pass_secret_for(buf, &exit_code);
+      if (!secret || !(WIFEXITED(exit_code) && WEXITSTATUS(exit_code) == 0)) {
+        secret = "";
+      }
+      if (write(cfd, secret, strlen(secret) + 1) != strlen(secret) + 1) {
+        fprintf(stderr, "%s\n", "write() failed!");
+      }
+    }
+
+    if (close(cfd) == -1) {
+      fprintf(stderr, "%s\n", "close() failed!");
+    }
+  }
+  close(sfd);
+  destroy_cache();
+  free_socket_path(spath);
+}
-- 
cgit v1.2.3