diff options
author | lamp | 2023-03-05 21:48:37 +0000 |
---|---|---|
committer | lamp | 2023-03-05 21:48:37 +0000 |
commit | 998b06d89b869b9587830ec767c6e30f851fff29 (patch) | |
tree | 08b4a4478dc224abec092611fed40b79769cb216 |
initmain
-rw-r--r-- | README.md | 18 | ||||
-rw-r--r-- | common.h | 40 | ||||
-rw-r--r-- | pass-cache.c | 51 | ||||
-rw-r--r-- | passcached.c | 175 |
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); +} |