aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/libnpass/gpg.c130
-rw-r--r--src/libnpass/libnpass.c202
-rw-r--r--src/libnpass/meson.build11
-rw-r--r--src/libnpass/util.c66
-rw-r--r--src/npass/meson.build8
-rw-r--r--src/npass/npass.c133
6 files changed, 550 insertions, 0 deletions
diff --git a/src/libnpass/gpg.c b/src/libnpass/gpg.c
new file mode 100644
index 0000000..574a492
--- /dev/null
+++ b/src/libnpass/gpg.c
@@ -0,0 +1,130 @@
+#include <stdio.h>
+#include <errno.h>
+#include <locale.h>
+
+#include <gpgme.h>
+
+#include "gpg.h"
+#include "util.h"
+
+#define fail_if_err(err) \
+ if (err) { \
+ fprintf(stderr, "%s:%d: %s: %s\n", __FILE__, __LINE__, \
+ gpgme_strsource(err), gpgme_strerror(err)); \
+ gpg_cleanup(); \
+ return 1; \
+ }
+
+static gpgme_ctx_t ctx = NULL;
+static gpgme_key_t key = NULL;
+
+int gpg_init(void);
+void gpg_cleanup(void);
+
+int gpg_init(void)
+{
+ gpgme_error_t err;
+ const char *local = setlocale(LC_ALL, "");
+
+ gpgme_check_version(NULL);
+ gpgme_set_locale(NULL, LC_CTYPE, local);
+#ifdef LC_MESSAGES
+ gpgme_set_locale(NULL, LC_MESSAGES, local);
+#endif
+
+ err = gpgme_engine_check_version(GPGME_PROTOCOL_OpenPGP);
+ fail_if_err(err);
+ err = gpgme_new(&ctx);
+ fail_if_err(err);
+
+ return 0;
+}
+
+void gpg_cleanup(void)
+{
+ if (ctx)
+ gpgme_key_release(key);
+ if (key)
+ gpgme_release(ctx);
+}
+
+int gpg_key_validate(const char *fpr)
+{
+ int r;
+ gpgme_error_t err;
+
+ r = gpg_init();
+ if (r)
+ return r;
+
+ err = gpgme_get_key(ctx, fpr, &key, 1);
+ fail_if_err(err);
+
+ gpg_cleanup();
+ return 0;
+}
+
+int gpg_decrypt(FILE *pass_out, const char *pass_path)
+{
+ int r;
+ char buf[BUFSIZ];
+ gpgme_data_t in, out;
+ gpgme_error_t err;
+
+ r = gpg_init();
+ if (r)
+ return r;
+
+ err = gpgme_data_new_from_file(&in, pass_path, 1);
+ fail_if_err(err);
+ err = gpgme_data_new(&out);
+ fail_if_err(err);
+ err = gpgme_op_decrypt(ctx, in, out);
+ fail_if_err(err);
+
+ r = gpgme_data_seek(out, 0, SEEK_SET);
+ if (r)
+ fail_if_err (gpgme_err_code_from_errno(errno));
+
+ while ((r = gpgme_data_read(out, buf, sizeof(buf))))
+ fwrite(buf, r, 1, pass_out);
+ if (r < 0)
+ fail_if_err(gpgme_err_code_from_errno(errno));
+
+ gpg_cleanup();
+ return 0;
+}
+
+int gpg_encrypt(FILE *stream, const char *fpr, const char *pass, size_t n)
+{
+ int r;
+ char buf[BUFSIZ];
+ gpgme_data_t in, out;
+ gpgme_error_t err;
+
+ r = gpg_init();
+ if (r)
+ return r;
+
+ err = gpgme_get_key(ctx, fpr, &key, 1);
+ fail_if_err(err);
+
+ err = gpgme_data_new_from_mem(&in, pass, n, 0);
+ fail_if_err(err);
+ err = gpgme_data_new(&out);
+ fail_if_err(err);
+ err = gpgme_op_encrypt(ctx, &key, GPGME_ENCRYPT_ALWAYS_TRUST, in, out);
+ fail_if_err(err);
+
+ r = gpgme_data_seek(out, 0, SEEK_SET);
+ if (r)
+ fail_if_err (gpgme_err_code_from_errno(errno));
+
+ while ((r = gpgme_data_read(out, buf, sizeof(buf))))
+ fwrite(buf, r, 1, stream);
+ gpg_cleanup();
+ if (r < 0)
+ fail_if_err(gpgme_err_code_from_errno(errno));
+
+ return 0;
+}
diff --git a/src/libnpass/libnpass.c b/src/libnpass/libnpass.c
new file mode 100644
index 0000000..72e51df
--- /dev/null
+++ b/src/libnpass/libnpass.c
@@ -0,0 +1,202 @@
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <linux/limits.h>
+#include <libgen.h>
+#include <termios.h>
+#include <stdio.h>
+
+#include "libnpass.h"
+#include "util.h"
+#include "gpg.h"
+
+#define DEF_PASS_DIR "pass"
+#define FPR_MAX 256
+
+static char pass_dir[PATH_MAX] = {0};
+
+int set_pass_dir(void);
+
+int set_pass_dir(void) {
+ const char *env;
+
+ env = getenv("PASSWORD_STORE_DIR");
+ if (env) {
+ strncpy(pass_dir, env, sizeof(pass_dir) - 1);
+ return 0;
+ }
+
+ env = getenv("XDG_DATA_HOME");
+ if (env) {
+ snprintf(pass_dir, sizeof(pass_dir) - 1, "%s/%s", env,
+ DEF_PASS_DIR);
+ return 0;
+ }
+
+ env = getenv("HOME");
+ if (env) {
+ snprintf(pass_dir, sizeof(pass_dir) - 1, "%s/%s/%s", env,
+ ".local/share", DEF_PASS_DIR);
+ return 0;
+ }
+
+ return 1;
+}
+
+int pass_init(const char *fpr)
+{
+ int r;
+ char gpg_id_path[PATH_MAX];
+ FILE *gpg_id;
+
+ r = set_pass_dir();
+ if (r)
+ err_die(1, "PASSWORD_STORE_DIR not set");
+
+ r = gpg_key_validate(fpr);
+ if (r)
+ err_die(1, "key not usable, try gpg --full-generate-key");
+
+
+ r = r_mkdir(pass_dir, S_IRWXU);
+ if (r)
+ err_die(1, "%s %s", pass_dir, strerror(errno));
+
+ r = snprintf(gpg_id_path, sizeof(gpg_id_path), "%s/%s", pass_dir, ".gpg-id");
+ if (r > (int) sizeof(gpg_id_path))
+ err_die(1, "path exceeded PATH_MAX");
+
+ gpg_id = fopen(gpg_id_path, "w");
+ if (!gpg_id)
+ err_die(1, "%s %s", gpg_id_path, strerror(errno));
+
+ r = fwrite(fpr, strlen(fpr), 1,gpg_id);
+ fclose(gpg_id);
+ if (!r)
+ err_die(1, "write failed");
+
+ return 0;
+}
+
+int pass_cat(FILE *out, const char *path)
+{
+ int r;
+ char pass_path[PATH_MAX];
+
+ r = set_pass_dir();
+ if (r)
+ err_die(1, "PASSWORD_STORE_DIR not set");
+
+ r = snprintf(pass_path, sizeof(pass_path), "%s/%s.gpg", pass_dir, path);
+ if (r >= (int) sizeof(pass_path))
+ err_die(1, "path exceeded PATH_MAX");
+
+ r = gpg_decrypt(out, pass_path);
+ return r;
+}
+
+ssize_t pass_getpass(char **lineptr, size_t *n, FILE *stream)
+{
+ ssize_t r;
+ struct termios new, old;
+
+ r = tcgetattr(fileno(stream), &old);
+ if (r)
+ return -1;
+
+ new = old;
+ new.c_lflag &= ~ECHO;
+ r = tcsetattr(fileno(stream), TCSAFLUSH, &new);
+ if (r)
+ return -1;
+
+ r = getline (lineptr, n, stream);
+ if (r > 0 && (*lineptr)[r - 1] == '\n')
+ (*lineptr)[r - 1] = '\0';
+
+ (void) tcsetattr (fileno (stream), TCSAFLUSH, &old);
+
+ return r;
+}
+
+int pass_add(const char *path, const char *pass, size_t n)
+{
+ int r;
+ char *rc;
+ FILE *gpg_id, *out_stream;
+ char gpg_id_path[PATH_MAX], fpr[FPR_MAX], pass_path[PATH_MAX];
+
+ r = set_pass_dir();
+ if (r)
+ err_die(1, "PASSWORD_STORE_DIR not set");
+
+ r = snprintf(gpg_id_path, sizeof(gpg_id_path), "%s/%s", pass_dir, ".gpg-id");
+ if (r > (int) sizeof(gpg_id_path))
+ err_die(1, "path exceeded PATH_MAX");
+
+ gpg_id = fopen(gpg_id_path, "r");
+ if (!gpg_id)
+ err_die(1, "%s %s", gpg_id_path, strerror(errno));
+
+ rc = fgets(fpr, sizeof(fpr), gpg_id);
+ if (!rc)
+ err_die(1, "failed to read %s", gpg_id_path);
+ fclose(gpg_id);
+ util_strtrim(fpr);
+
+ r = gpg_key_validate(fpr);
+ if (r)
+ err_die(1, "invalid key , try gpg --list-keys");
+
+ // TODO: guard against .*\.gpg\.gpg[/$]
+ r = snprintf(pass_path, sizeof(pass_path), "%s/%s.gpg", pass_dir, path);
+ if (r > (int) sizeof(pass_path))
+ err_die(1, "path exceeded PATH_MAX");
+
+ rc = strdup(pass_path);
+ if (!rc)
+ err_die(1, "%s", strerror(errno));
+ (void) r_mkdir(dirname(rc), S_IRWXU);
+ free(rc);
+
+ r = access(pass_path, F_OK);
+ if (!(errno & ENOENT))
+ err_die(1, "an entry already exists for %s", path);
+
+ out_stream = fopen(pass_path, "w");
+ if (!out_stream)
+ err_die(1, "%s", strerror(errno));
+
+ r = gpg_encrypt(out_stream, fpr, pass, n);
+
+ fclose(out_stream);
+ return r;
+}
+
+int pass_rm(const char *path)
+{
+
+ int r = 0;
+ char gpg_path[PATH_MAX], abs_path[PATH_MAX];
+
+ r = set_pass_dir();
+ if (r)
+ err_die(1, "PASSWORD_STORE_DIR not set");
+
+ r = snprintf(gpg_path, sizeof(gpg_path), "%s.gpg", path);
+ if (r > (int) sizeof(gpg_path))
+ err_die(1, "path exceeded PATH_MAX");
+
+ r = snprintf(abs_path, sizeof(gpg_path), "%s/%s", pass_dir, gpg_path);
+ if (r > (int) sizeof(abs_path))
+ err_die(1, "path exceeded PATH_MAX");
+
+ // TODO: guard against .*\.gpg\.gpg[/$]
+ r = unlink(abs_path);
+ if (r)
+ err_die(1, "%s %s", abs_path, strerror(errno));
+
+ return r_rmdir(pass_dir, dirname(gpg_path));
+}
diff --git a/src/libnpass/meson.build b/src/libnpass/meson.build
new file mode 100644
index 0000000..1ce4621
--- /dev/null
+++ b/src/libnpass/meson.build
@@ -0,0 +1,11 @@
+lib_npass = static_library(
+ 'libnpass',
+ [
+ 'util.c',
+ 'gpg.c',
+ 'libnpass.c'
+ ],
+
+ dependencies: gpgme_dep,
+ include_directories: npass_inc,
+)
diff --git a/src/libnpass/util.c b/src/libnpass/util.c
new file mode 100644
index 0000000..8e3a108
--- /dev/null
+++ b/src/libnpass/util.c
@@ -0,0 +1,66 @@
+#include <sys/stat.h>
+#include <string.h>
+#include <errno.h>
+#include <linux/limits.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <libgen.h>
+
+#include "util.h"
+
+int r_mkdir(const char *path, mode_t mode)
+{
+ int r;
+ size_t len;
+ char *p;
+ char tmp[NAME_MAX + 1];
+
+ strncpy(tmp, path, sizeof(tmp) - 1);
+ len = strlen(tmp);
+ if(tmp[len - 1] == '/')
+ tmp[len - 1] = '\0';
+
+ for (p = tmp + 1; *p; ++p) {
+ if (*p == '/') {
+ *p = '\0';
+
+ r = mkdir(tmp, mode);
+ if (r && !(errno & EEXIST))
+ return r;
+
+ *p = '/';
+ }
+ }
+
+ return mkdir(path, mode);
+}
+
+int r_rmdir(const char *prefix_path, char *rm_path)
+{
+ int r;
+ char abs_path[PATH_MAX];
+
+ if (!strcmp(rm_path, "."))
+ return 0;
+
+ r = snprintf(abs_path, sizeof(abs_path), "%s/%s", prefix_path, rm_path);
+ if (r > (int) sizeof(abs_path))
+ err_die(1, "path exceeded PATH_MAX");
+
+ r = rmdir(abs_path);
+ if (r && errno != EEXIST && errno != ENOTEMPTY)
+ err_die(1, "%s", strerror(errno));
+
+ return r_rmdir(prefix_path, dirname(rm_path));
+}
+
+void util_strtrim(char *s)
+{
+ char *rend;
+
+ for (rend = s; *s; ++s)
+ if (!isspace(*s))
+ rend = s;
+
+ rend[1] = '\0';
+}
diff --git a/src/npass/meson.build b/src/npass/meson.build
new file mode 100644
index 0000000..2371298
--- /dev/null
+++ b/src/npass/meson.build
@@ -0,0 +1,8 @@
+executable(
+ 'pass',
+ 'npass.c',
+
+ include_directories: npass_inc,
+ link_with: lib_npass,
+ install: true,
+)
diff --git a/src/npass/npass.c b/src/npass/npass.c
new file mode 100644
index 0000000..8aacc14
--- /dev/null
+++ b/src/npass/npass.c
@@ -0,0 +1,133 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "libnpass.h"
+#include "util.h"
+
+void print_usage(void);
+int cat(const char *path);
+int add(const char *path);
+
+void print_usage(void)
+{
+ printf("Usage: pass COMMAND\n\n"
+
+ "Commands:\n"
+ " init key-id/fingerprint\n"
+ " Initialize new password storage\n"
+ " ls [ pass-path ]\n"
+ " List passwords\n"
+ " rm pass-name\n"
+ " Remove password\n"
+ " add pass-name\n"
+ " Add new password\n"
+ " gen pass-name\n"
+ " Generate new password\n"
+ " cat pass-name\n"
+ " Show encrypted password\n"
+ " help\n"
+ " Show this help\n");
+}
+
+int cat(const char *path)
+{
+ int r;
+
+ r = pass_cat(stdout, path);
+ if (!r && isatty(STDOUT_FILENO))
+ putchar('\n');
+
+ return r;
+}
+
+int add(const char *path)
+{
+ char *p1 = NULL, *p2 = NULL;
+ FILE *in;
+ size_t n;
+ int r;
+
+ in = fopen("/dev/tty", "r");
+ if (!in)
+ in = stdin;
+
+ fputs("Password: ", stdout);
+ r = pass_getpass(&p1, &n, in);
+ if (r < 0) {
+ if (in != stdin)
+ fclose(in);
+ err_die(1, "%d:%s:", errno, strerror(errno));
+ }
+
+ fputs("\nRetype password: ", stdout);
+ r = pass_getpass(&p2, &n, in);
+ putc('\n', stdout);
+ if (r < 0) {
+ if (in != stdin)
+ fclose(in);
+ if (p1)
+ free(p1);
+ err_die(1, "%d:%s:", errno, strerror(errno));
+ }
+
+ if (in != stdin)
+ fclose(in);
+
+ if (strcmp(p1, p2)) {
+ free(p1);
+ free(p2);
+ err_die(1, "Sorry, passwords do not match");
+ }
+
+ free(p1);
+ r = pass_add(path, p2, n);
+
+ free(p2);
+ return r;
+}
+
+int main(int argc, char *argv[])
+{
+ int r = 0;
+
+ if (!--argc) {
+ print_usage();
+ exit(EXIT_FAILURE);
+ }
+ ++argv;
+
+
+ if (!strcmp("help", *argv)) {
+ print_usage();
+ } else if (!strcmp("init", *argv)) {
+ if (argc != 2)
+ err_die(1, "invalid usage, try pass help");
+
+ r = pass_init(argv[1]);
+ } else if (!strcmp("cat", *argv)) {
+ if (argc != 2)
+ err_die(1, "invalid usage, try pass help");
+
+ r = cat(argv[1]);
+ } else if (!strcmp("add", *argv)) {
+ if (argc != 2)
+ err_die(1, "invalid usage, try pass help");
+
+ r = add(argv[1]);
+ } else if (!strcmp("rm", *argv)) {
+ if (argc != 2)
+ err_die(1, "invalid usage, try pass help");
+
+ r = pass_rm(argv[1]);
+ } else {
+ r = cat(*argv);
+ }
+
+ if (r)
+ err_die(r, "Command failed");
+
+ return 0;
+}