npm

Unnamed repository; edit this file 'description' to name the repository.
git clone git://git.nihaljere.xyz/npm
Log | Files | Refs

commit c1ffd73c262192a0b748f2e2df2bbd9259ec3a65
parent 9556bb6bbab333a2775a9ed42146eae69e75dbeb
Author: Nihal Jere <nihal@nihaljere.xyz>
Date:   Thu, 26 Aug 2021 20:24:05 -0500

npm-agent + npmc draft

Diffstat:
MMakefile | 13++++++++++---
Anpm-agent.c | 319+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anpmc.c | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 415 insertions(+), 3 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,10 +1,17 @@ LIBS = argon2/argon2.a SRC = chacha20.c npm.c util.c OBJ = $(SRC:%.c=%.o) -EXE = npm-core -$(EXE): $(LIBS) $(OBJ) - $(CC) $(OBJ) $(LIBS) -o $@ +all: npm-core npm-agent npmc + +npm-core: $(LIBS) chacha20.o npm.o util.o + $(CC) -static chacha20.o npm.o util.o $(LIBS) -o $@ + +npm-agent: npm-agent.o + $(CC) -static npm-agent.o -o $@ + +npmc: npmc.o + $(CC) -static npmc.o -o $@ .c.o: $(CC) -c $< -o $@ diff --git a/npm-agent.c b/npm-agent.c @@ -0,0 +1,319 @@ +#define _BSD_SOURCE + +/* npm-agent uses a simple socket protocol to receive and respond to requests: + * a client sends a null-terminated path over a socket, with a maximum length + * PATH_MAX. + * npm-agent responds with a null-terminated password with a max length of + * PASSWORD_MAX_LEN characters. + */ + +#include <errno.h> +#include <limits.h> +#include <poll.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/timerfd.h> +#include <sys/un.h> +#include <sys/wait.h> +#include <unistd.h> +#include <errno.h> + +#include "common.h" + +#ifndef SOCKPATH +#define SOCKPATH "/tmp/npm-agent" +#endif + +#ifndef TIMEOUT +#define TIMEOUT 5000 +#endif + +#define LISTENER 0 +#define TIMER 1 +#define CLIENT 2 + +char *corecmd[] = { "./npm-core", "-d", NULL, NULL }; +//char *const getpasscmd[] = { "dmenu", "-P", "-p", "Password:", NULL }; +char *const getpasscmd[] = { "bemenu", "-x", "-p", "Password:", NULL }; + +bool cached = false; + +char inbuf[PATH_MAX]; +char encryptor[PASSWORD_MAX_LEN+1]; +size_t encryptorlen; +char *inptr = inbuf; +size_t inlen; +struct pollfd fds[3]; + +int cstdin, cstdout; // stdin, out from the core + +int +xwrite(int fd, char *buf, size_t count) +{ + ssize_t ret; + char *ptr = buf; + while (count) { + ret = write(fd, ptr, count); + if (ret == -1) + return -1; + + count -= ret; + ptr += ret; + } + + return count; +} + +int +read_to_nl(int fd, char *buf) +{ + fprintf(stderr, "%s\n", __func__); + ssize_t ret; + size_t len = 0; + char *ptr = buf; + while (ret && len <= PASSWORD_MAX_LEN && !memchr(buf, '\n', len)) { + ret = read(fd, ptr, PASSWORD_MAX_LEN - len); + if (ret == -1) + return ret; + + len += ret; + ptr += ret; + } + + return len; +} + +void +clear_encryptor() +{ + cached = false; + explicit_bzero(encryptor, sizeof(encryptor)); + encryptorlen = 0; +} + +int +get_password() +{ + fprintf(stderr, "%s\n", __func__); + int stdoutpipe[2], stdinpipe[2], status; + + if (pipe(stdoutpipe) == -1) { + fprintf(stderr, "failed to create stdout pipe: %s\n", strerror(errno)); + } + + if (pipe(stdinpipe) == -1) { + fprintf(stderr, "failed to create stdin pipe: %s\n", strerror(errno)); + } + + pid_t pid = fork(); + switch (pid) { + case -1: + fprintf(stderr, "fork failed\n"); + return -1; + case 0: + close(stdoutpipe[0]); + dup2(stdoutpipe[1], 1); + close(stdinpipe[1]); + dup2(stdinpipe[0], 0); + + if (execvp(getpasscmd[0], getpasscmd) == -1) + fprintf(stderr, "exec failed: %s\n", strerror(errno)); + + break; + default: + close(stdoutpipe[1]); + close(stdinpipe[0]); + close(stdinpipe[1]); + + if ((encryptorlen = read_to_nl(stdoutpipe[0], encryptor)) == -1) { + fprintf(stderr, "failed to read password from pipe\n"); + return -1; + } + + close(stdoutpipe[0]); + waitpid(pid, &status, 0); + } + + cached = true; + + return status; +} + +int +run_core() +{ + fprintf(stderr, "%s\n", __func__); + int stdinpipe[2], status; + if (pipe(stdinpipe) == -1) { + fprintf(stderr, "failed to create stdin pipe: %s\n", strerror(errno)); + } + + pid_t pid = fork(); + switch (pid) { + case -1: + fprintf(stderr, "fork failed\n"); + return -1; + case 0: + close(stdinpipe[1]); + + dup2(stdinpipe[0], 0); + dup2(fds[CLIENT].fd, 1); + + corecmd[2] = inbuf; + fprintf(stderr, "path is: %s\n", corecmd[2]); + if (execvp(corecmd[0], corecmd) == -1) + fprintf(stderr, "exec failed: %s\n", strerror(errno)); + + break; + default: + close(stdinpipe[0]); + + if (xwrite(stdinpipe[1], encryptor, encryptorlen) == -1) { + fprintf(stderr, "failed to write password to pipe\n"); + return -1; + } + + close(stdinpipe[1]); + + waitpid(pid, &status, 0); + } + + return status; +} + +const struct itimerspec timerspec = { + .it_interval = {0}, + .it_value = { TIMEOUT/1000, (TIMEOUT % 1000) * 1000 * 1000 }, +}; + +void +set_timer() +{ + if (timerfd_settime(fds[TIMER].fd, 0, &timerspec, NULL) == -1) { + fprintf(stderr, "failed to set cache timeout: %s\n", strerror(errno)); + } +} + +int +agent() +{ + fprintf(stderr, "%s\n", __func__); + + if (!cached) { + get_password(); + set_timer(); + } + + run_core(); +} + +void +remove_socket(void) +{ + unlink(SOCKPATH); +} + +int +main(int argc, char *argv[]) +{ + struct sockaddr_un sockaddr = { + .sun_family = AF_UNIX, + .sun_path = SOCKPATH + }; + int ret; + + int sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock == -1) { + fprintf(stderr, "failed to create socket: %s\n", strerror(errno)); + goto error_socket; + } + + if (bind(sock, (struct sockaddr *) &sockaddr, sizeof(sockaddr)) == -1) { + fprintf(stderr, "failed to bind to socket: %s\n", strerror(errno)); + goto error; + } + + if (atexit(&remove_socket) != 0) + goto error; + + if (listen(sock, 50) == -1) { + fprintf(stderr, "failed to set socket to listening: %s\n", strerror(errno)); + goto error; + } + + int timer = timerfd_create(CLOCK_MONOTONIC, 0); + if (!timer) { + fprintf(stderr, "failed to create timerfd: %s\n", strerror(errno)); + goto error; + } + + fds[LISTENER].fd = sock; + fds[LISTENER].events = POLLIN; + fds[TIMER].fd = timer; + fds[TIMER].events = POLLIN; + fds[CLIENT].fd = -1; + + while (1) { + if (poll(fds, sizeof(fds) / sizeof(fds[0]), -1) == -1) { + fprintf(stderr, "poll failed: %s", strerror(errno)); + goto error; + } + + // new connection + if (fds[LISTENER].revents & POLLIN) { + if (fds[CLIENT].fd < 0) { + fds[CLIENT].fd = accept(fds[LISTENER].fd, NULL, NULL); + if (fds[CLIENT].fd == -1) + fprintf(stderr, "accept failed: %s", strerror(errno)); + + inptr = inbuf; + inlen = 0; + memset(inbuf, 0, sizeof(inbuf)); + fds[CLIENT].events = POLLIN; + } + } + + // timer expired + if (fds[TIMER].revents & POLLIN) { + uint64_t val; + read(fds[TIMER].fd, &val, sizeof(val)); + clear_encryptor(); + } + + // incoming data + if (fds[CLIENT].revents & POLLIN) { + ret = read(fds[CLIENT].fd, inptr, sizeof(inbuf) - inlen); + if (ret == -1) { + fprintf(stderr, "failed to read from client: %s\n", strerror(errno)); + goto error; + } + + inlen += ret; + inptr += ret; + // if there is a null, the path is complete + if (memchr(inbuf, 0, inlen)) { + fds[CLIENT].revents &= ~POLLIN; + + agent(); + close(fds[CLIENT].fd); + fds[CLIENT].fd = -1; + continue; + } + // if the buffer is full without a null, the client is misbehaving, + // so close the connection and clear inbuf + if (inlen == sizeof(inbuf)) { + close(fds[CLIENT].fd); + fds[CLIENT].fd = -1; + } + } + } + +error: + close(sock); +error_socket: + return 1; +} diff --git a/npmc.c b/npmc.c @@ -0,0 +1,86 @@ +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <errno.h> + +#include "common.h" + +#ifndef SOCKPATH +#define SOCKPATH "/tmp/npm-agent" +#endif + +char answer[PASSWORD_MAX_LEN + 1]; +int answerlen = 0; + +int +xwrite(int fd, char *buf, size_t count) +{ + ssize_t ret; + char *ptr = buf; + while (count) { + ret = write(fd, ptr, count); + if (ret == -1) + return -1; + + count -= ret; + ptr += ret; + } + + return count; +} + +int +read_answer(int fd) +{ + fprintf(stderr, "%s\n", __func__); + ssize_t ret; + char *ptr = answer; + while (answerlen <= PASSWORD_MAX_LEN && !memchr(answer, '\n', answerlen)) { + ret = read(fd, ptr, PASSWORD_MAX_LEN - answerlen); + fprintf(stderr, "ret = %d\n", ret); + if (ret == -1) + return ret; + + answerlen += ret; + ptr += ret; + } + + return answerlen; +} + +int +main(int argc, char *argv[]) +{ + const struct sockaddr_un sockaddr = { + .sun_family = AF_UNIX, + .sun_path = SOCKPATH + }; + if (argc != 2) + return 1; + + int sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock == -1) { + fprintf(stderr, "failed to create socket: %s\n", strerror(errno)); + goto end; + } + + if (connect(sock, (struct sockaddr *) &sockaddr, sizeof(sockaddr)) == -1) { + fprintf(stderr, "failed to connect to socket: %s\n", strerror(errno)); + goto closesock; + } + + xwrite(sock, argv[1], strlen(argv[1]) + 1); // include terminator + + read_answer(sock); + + puts(answer); + +closesock: + close(sock); +end: + return 0; +}