aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md18
-rw-r--r--common.h40
-rw-r--r--pass-cache.c51
-rw-r--r--passcached.c175
4 files changed, 284 insertions, 0 deletions
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);
+}