summaryrefslogtreecommitdiff
path: root/trans/gpg.c
diff options
context:
space:
mode:
Diffstat (limited to 'trans/gpg.c')
-rw-r--r--trans/gpg.c682
1 files changed, 682 insertions, 0 deletions
diff --git a/trans/gpg.c b/trans/gpg.c
new file mode 100644
index 00000000..abdea7cb
--- /dev/null
+++ b/trans/gpg.c
@@ -0,0 +1,682 @@
+/* gpg -- a translator for encrypting, decrypting, and verifying files
+ Copyright (C) 2016 Free Software Foundation, Inc.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2, or (at
+ your option) any later version.
+
+ This program is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
+
+#include <argz.h>
+#include <argp.h>
+#include <dirent.h>
+#include <error.h>
+#include <hurd/netfs.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <pthread.h>
+#include <hurd/ihash.h>
+#include <hurd/paths.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <version.h>
+
+#include "chroot.h"
+
+#include "libnetfs/fs_S.h"
+#include "libnetfs/io_S.h"
+#include "libnetfs/fsys_S.h"
+#include "libports/notify_S.h"
+#include "libports/interrupt_S.h"
+
+const char *argp_program_version = STANDARD_HURD_VERSION (gpg);
+
+char *netfs_server_name = "gpg";
+char *netfs_server_version = HURD_VERSION;
+int netfs_maxsymlinks = 16; /* arbitrary */
+
+struct chroot_node
+{
+ unsigned int encrypt:1;
+ file_t shadow_file;
+};
+
+#define GPG_DEFAULT_FILENAME "/usr/bin/gpg2"
+static char* gpg_filename = GPG_DEFAULT_FILENAME;
+static file_t gpg_file;
+
+int symmetric;
+char *recipients;
+size_t recipients_len;
+
+static error_t
+execute_gpg (mach_port_t in, mach_port_t out, mach_port_t extra,
+ char *argz, size_t argz_len, pid_t *pid)
+{
+ error_t err;
+ task_t task;
+ process_t proc;
+ mach_port_t ports[INIT_PORT_MAX];
+ mach_port_t fds[STDERR_FILENO + 2];
+ int ints[INIT_INT_MAX];
+ int i;
+
+ for (i = 0; i < INIT_PORT_MAX; i++)
+ ports[i] = MACH_PORT_NULL;
+ for (i = 0; i < STDERR_FILENO + 2; i++)
+ fds[i] = MACH_PORT_NULL;
+ memset (ints, 0, INIT_INT_MAX * sizeof(int));
+
+ ports[INIT_PORT_CWDIR] = getcwdir ();
+ ports[INIT_PORT_CRDIR] = getcrdir ();
+ ports[INIT_PORT_AUTH] = getauth ();
+ fds[STDIN_FILENO] = in;
+ fds[STDOUT_FILENO] = out;
+ fds[STDERR_FILENO] = getdport (STDERR_FILENO);
+ fds[STDERR_FILENO+1] = extra;
+
+ err = task_create (mach_task_self (), 0, &task);
+ if (err)
+ goto lose;
+
+ proc = getproc ();
+ proc_child (proc, task);
+
+ /* Try and exec GnuPG in TASK... */
+ err = file_exec (gpg_file, task, EXEC_DEFAULTS,
+ argz, argz_len, 0, 0,
+ fds, MACH_MSG_TYPE_COPY_SEND, STDERR_FILENO + 2,
+ ports, MACH_MSG_TYPE_COPY_SEND, INIT_PORT_MAX,
+ ints, INIT_INT_MAX, 0, 0, 0, 0);
+ if (err)
+ goto lose;
+
+ err = proc_task2pid (proc, task, pid);
+
+ lose:
+ for (i = 0; i < INIT_PORT_MAX; i++)
+ mach_port_deallocate (mach_task_self (), ports[i]);
+ mach_port_deallocate (mach_task_self (), fds[STDERR_FILENO]);
+ return err;
+}
+
+error_t
+clone_file (file_t file, file_t *new_file)
+{
+ error_t err;
+ off_t newp;
+ retry_type do_retry;
+ string_t retry_name;
+ err = dir_lookup (file, "", O_RDONLY, 0, &do_retry, retry_name, new_file);
+ if (err)
+ return err;
+ return io_seek (*new_file, 0, SEEK_SET, &newp);
+}
+
+static int
+gpg_verify (file_t sigfile, file_t orig_file)
+{
+ error_t err;
+ file_t file;
+ pid_t pid;
+ char *argz = NULL;
+ size_t argz_len = 0;
+ err = clone_file (orig_file, &file);
+ if (err)
+ return 0;
+
+ argz_add (&argz, &argz_len, "gpg2");
+ argz_add (&argz, &argz_len, "--verify");
+ argz_add (&argz, &argz_len, "/dev/fd/3");
+ argz_add (&argz, &argz_len, "-");
+ err = execute_gpg (file, getdport (STDERR_FILENO), sigfile, argz, argz_len, &pid);
+ mach_port_deallocate (mach_task_self (), file);
+ if (err)
+ {
+ error (0, err, "execute_gpg");
+ return 0;
+ }
+
+ int status;
+ while (waitpid (pid, &status, 0) == (pid_t)-1 && errno == EINTR)
+ { }
+
+ return WIFEXITED (status) && WEXITSTATUS (status) == 0;
+}
+
+static int
+gpg_decrypt (file_t cipherfile, file_t plainfile)
+{
+ error_t err;
+ pid_t pid;
+ char *argz = NULL;
+ size_t argz_len = 0;
+
+ argz_add (&argz, &argz_len, "gpg2");
+ argz_add (&argz, &argz_len, "--output");
+ argz_add (&argz, &argz_len, "-");
+ err = execute_gpg (cipherfile, plainfile, MACH_PORT_NULL,
+ argz, argz_len, &pid);
+ if (err)
+ {
+ error (0, err, "execute_gpg");
+ return 0;
+ }
+
+ int status;
+ while (waitpid (pid, &status, 0) == (pid_t)-1 && errno == EINTR)
+ { }
+
+ return WIFEXITED (status) && WEXITSTATUS (status) == 0;
+}
+
+static int
+gpg_encrypt (file_t plainfile, file_t cipherfile)
+{
+ error_t err;
+ pid_t pid;
+ char *argz = NULL;
+ size_t argz_len = 0;
+
+ argz_add (&argz, &argz_len, "gpg2");
+ argz_add (&argz, &argz_len, "--output");
+ argz_add (&argz, &argz_len, "-");
+ if (recipients)
+ {
+ char *recipient;
+ argz_add (&argz, &argz_len, "--encrypt");
+ for (recipient = recipients;
+ recipient;
+ recipient = argz_next (recipients, recipients_len, recipient))
+ {
+ argz_add (&argz, &argz_len, "--recipient");
+ argz_add (&argz, &argz_len, recipient);
+ }
+ }
+ if (symmetric)
+ argz_add (&argz, &argz_len, "--symmetric");
+
+ err = execute_gpg (plainfile, cipherfile, MACH_PORT_NULL,
+ argz, argz_len, &pid);
+ if (err)
+ {
+ error (0, err, "execute_gpg");
+ return 0;
+ }
+
+ int status;
+ while (waitpid (pid, &status, 0) == (pid_t)-1 && errno == EINTR)
+ { }
+
+ return WIFEXITED (status) && WEXITSTATUS (status) == 0;
+}
+
+static error_t
+make_tmp_file (file_t *file)
+{
+ char name[] = "/tmp/gpgtmp-XXXXXX";
+ int fd;
+
+ fd = mkstemp (name);
+ if (fd == -1)
+ return errno;
+ if (unlink (name) == -1)
+ return errno;
+ *file = getdport (fd);
+ close (fd);
+ return 0;
+}
+
+error_t
+netfs_S_dir_lookup (struct protid *diruser,
+ char *filename,
+ int flags,
+ mode_t mode,
+ retry_type *do_retry,
+ char *retry_name,
+ mach_port_t *retry_port,
+ mach_msg_type_name_t *retry_port_type)
+{
+ struct node *dnp, *np;
+ error_t err;
+ struct protid *newpi;
+ struct iouser *user;
+ file_t file;
+ file_t dir;
+
+ if (! diruser)
+ return EOPNOTSUPP;
+
+ dnp = diruser->po->np;
+
+ dir = netfs_node_netnode (dnp)->file;
+ err = dir_lookup (dir, filename,
+ flags & (O_NOLINK|O_RDWR|O_EXEC|O_CREAT|O_EXCL|O_NONBLOCK),
+ mode, do_retry, retry_name, &file);
+ if (err == ENOENT)
+ {
+ file_t gpgfile;
+ char *gpgname;
+ asprintf (&gpgname, "%s.gpg", filename);
+ gpgfile = file_name_lookup_under (dir, gpgname, O_RDONLY, 0);
+ if (MACH_PORT_VALID (gpgfile))
+ {
+ file_t tmpfile;
+ int ok;
+ off_t newp;
+
+ err = make_tmp_file (&tmpfile);
+ if (err)
+ return err;
+
+ ok = gpg_decrypt (gpgfile, tmpfile);
+ mach_port_deallocate (mach_task_self (), gpgfile);
+ if (! ok)
+ {
+ mach_port_deallocate (mach_task_self (), tmpfile);
+ return EIO;
+ }
+
+ err = io_seek (tmpfile, 0, SEEK_SET, &newp);
+ if (err)
+ {
+ mach_port_deallocate (mach_task_self (), tmpfile);
+ return err;
+ }
+
+ *do_retry = FS_RETRY_NORMAL;
+ retry_name[0] = 0;
+ *retry_port = tmpfile;
+ *retry_port_type = MACH_MSG_TYPE_MOVE_SEND;
+ return 0;
+ }
+ free (gpgname);
+ }
+
+ if (err)
+ return err;
+
+ switch (*do_retry)
+ {
+ case FS_RETRY_NORMAL:
+ break;
+
+ case FS_RETRY_REAUTH:
+ error (0, 0, "FS_RETRY_REAUTH");
+ /* Fallthrough. */
+
+ case FS_RETRY_MAGICAL:
+ *retry_port = file;
+ *retry_port_type = MACH_MSG_TYPE_MOVE_SEND;
+ return 0;
+
+ default:
+ /* Invalid response to our dir_lookup request. */
+ if (MACH_PORT_VALID (file))
+ mach_port_deallocate (mach_task_self (), file);
+ *retry_port = MACH_PORT_NULL;
+ *retry_port_type = MACH_MSG_TYPE_COPY_SEND;
+ return EOPNOTSUPP;
+ }
+
+ /* We have a new port to an underlying node.
+ Find or make our corresponding virtual node. */
+
+ np = 0;
+
+ pthread_mutex_lock (&dnp->lock);
+ err = chroot_new_node (file, sizeof (struct chroot_node), &np);
+ pthread_mutex_unlock (&dnp->lock);
+ if (!err)
+ {
+ chroot_node (np)->encrypt = chroot_node (dnp)->encrypt;
+ chroot_node (np)->shadow_file = MACH_PORT_NULL;
+ err = netfs_validate_stat (np, diruser->user);
+ }
+ if (err)
+ goto lose;
+
+ if (chroot_node (np)->encrypt && flags & O_WRITE
+ && strncmp (filename, "dev/", 4) != 0)
+ {
+ char *ciphname;
+ asprintf (&ciphname, "%s.gpg", filename);
+ if (ciphname == NULL)
+ {
+ err = errno;
+ goto lose;
+ }
+
+ /* Fixup name. */
+ err = dir_link (dir, file, ciphname, 0);
+ free (ciphname);
+ if (err)
+ return err;
+
+ err = dir_unlink (dir, filename);
+ if (err)
+ return err;
+
+ err = make_tmp_file (&chroot_node (np)->shadow_file);
+ if (err)
+ return err;
+ }
+
+ if (retry_name[0] == 0)
+ {
+ file_t sig;
+ char *signame;
+
+ asprintf (&signame, "%s.sig", filename);
+ if (signame == NULL)
+ {
+ err = errno;
+ goto lose;
+ }
+
+ sig = file_name_lookup_under (dir, signame, O_RDONLY, 0);
+ if (MACH_PORT_VALID (sig))
+ {
+ if (! gpg_verify (sig, file))
+ err = EIO;
+ free (signame);
+ signame = NULL;
+ mach_port_deallocate (mach_task_self (), sig);
+ if (err)
+ goto lose;
+ }
+ free (signame);
+ }
+
+ assert (*do_retry == FS_RETRY_NORMAL);
+ flags &= ~(O_CREAT|O_EXCL|O_NOLINK|O_NOTRANS|O_NONBLOCK);
+
+ err = iohelp_dup_iouser (&user, diruser->user);
+ if (!err)
+ {
+ newpi = netfs_make_protid (netfs_make_peropen (np, flags, diruser->po),
+ user);
+ if (! newpi)
+ {
+ iohelp_free_iouser (user);
+ err = errno;
+ }
+ else
+ {
+ *retry_port = ports_get_right (newpi);
+ *retry_port_type = MACH_MSG_TYPE_MAKE_SEND;
+ ports_port_deref (newpi);
+ }
+ }
+
+ lose:
+ if (dir != netfs_node_netnode (dnp)->file)
+ mach_port_deallocate (mach_task_self (), dir);
+ if (np != NULL)
+ netfs_nput (np);
+ return err;
+}
+
+error_t
+netfs_attempt_mkfile (struct iouser *user, struct node *dir,
+ mode_t mode, struct node **np)
+{
+ file_t newfile;
+ error_t err = dir_mkfile (netfs_node_netnode (dir)->file, O_RDWR|O_EXEC,
+ mode, &newfile);
+ pthread_mutex_unlock (&dir->lock);
+ if (err)
+ return err;
+
+ err = chroot_new_node (newfile, sizeof (struct chroot_node), np);
+ if (err)
+ return err;
+
+ if (chroot_node (dir)->encrypt)
+ {
+ err = make_tmp_file (&chroot_node (*np)->shadow_file);
+ if (err)
+ return err;
+ }
+ else
+ chroot_node (*np)->shadow_file = MACH_PORT_NULL;
+
+ pthread_mutex_unlock (&(*np)->lock);
+ return err;
+}
+
+error_t
+netfs_attempt_read (struct iouser *cred, struct node *np,
+ off_t offset, size_t *len, void *data)
+{
+ struct chroot_node *n = chroot_node (np);
+ char *buf = data;
+ error_t err = io_read (n->shadow_file ?: netfs_node_netnode (np)->file,
+ &buf, len, offset, *len);
+ if (err == 0 && buf != data)
+ {
+ memcpy (data, buf, *len);
+ munmap (buf, *len);
+ }
+ return err;
+}
+
+error_t
+netfs_attempt_write (struct iouser *cred, struct node *np,
+ off_t offset, size_t *len, void *data)
+{
+ struct chroot_node *n = chroot_node (np);
+ return io_write (n->shadow_file ?: netfs_node_netnode (np)->file,
+ data, *len, offset, len);
+}
+
+error_t
+netfs_attempt_sync (struct iouser *cred, struct node *np, int wait)
+{
+ error_t err;
+ file_t gpg_file = netfs_node_netnode (np)->file;
+ struct chroot_node *n = chroot_node (np);
+
+ err = file_sync (n->shadow_file ?: gpg_file, wait, 0);
+ if (err)
+ return err;
+
+ if (wait && MACH_PORT_VALID (n->shadow_file))
+ {
+ file_t shadow_file;
+ off_t newp;
+
+ err = clone_file (n->shadow_file, &shadow_file);
+ if (err)
+ return err;
+
+ err = io_seek (gpg_file, 0, SEEK_SET, &newp);
+ if (err)
+ return err;
+
+ err = gpg_encrypt (shadow_file, gpg_file);
+ if (err)
+ return err;
+
+ mach_port_deallocate (mach_task_self (), shadow_file);
+ err = file_sync (gpg_file, wait, 0);
+ }
+
+ return err;
+}
+
+
+error_t
+netfs_attempt_link (struct iouser *user, struct node *dir,
+ struct node *file, char *name, int excl)
+{
+ /* XXX translate name if file is encrypted. */
+ return dir_link (netfs_node_netnode (dir)->file,
+ netfs_node_netnode (file)->file, name, excl);
+}
+
+void
+chroot_node_norefs (struct node *np)
+{
+ error_t err;
+ file_t gpg_file = netfs_node_netnode (np)->file;
+ struct chroot_node *n = chroot_node (np);
+ if (n->encrypt)
+ {
+ off_t newp;
+
+ err = io_seek (n->shadow_file, 0, SEEK_SET, &newp);
+ if (err)
+ return;
+
+ err = io_seek (gpg_file, 0, SEEK_SET, &newp);
+ if (err)
+ return;
+
+ err = gpg_encrypt (n->shadow_file, gpg_file);
+ if (err)
+ return;
+
+ mach_port_deallocate (mach_task_self (), n->shadow_file);
+ }
+}
+
+
+error_t
+netfs_get_dirents (struct iouser *cred, struct node *dir,
+ int entry, int nentries, char **data,
+ mach_msg_type_number_t *datacnt,
+ vm_size_t bufsize, int *amt)
+{
+ error_t err;
+ char *ptr;
+ struct dirent *dent;
+ err = dir_readdir (netfs_node_netnode (dir)->file, data, datacnt,
+ entry, nentries, bufsize, amt);
+ if (err)
+ return err;
+
+ for (ptr = *data, dent = (struct dirent *) ptr;
+ ptr - *data < *datacnt;
+ ptr += dent->d_reclen, dent = (struct dirent *) ptr)
+ {
+ size_t len = strlen (dent->d_name);
+ if (len > 4 && strcmp (&dent->d_name[len - 4], ".gpg") == 0)
+ memset (&dent->d_name[len - 4], 0, 4);
+ }
+
+ return 0;
+}
+
+#define OPT_GPG_PROGRAM -1
+
+static struct argp_option options[] =
+{
+ {NULL, 0, NULL, 0, "How to encrypt:", 1},
+ {"recipient", 'r', "NAME", 0, "Encrypt for user NAME. "
+ "Can be given multiple times.", 1},
+ {"symmetric", 'c', NULL, 0, "Encrypt with a password.", 1},
+ {NULL, 0, NULL, 0, "Advanced options:", 2},
+ {"gpg-program", OPT_GPG_PROGRAM, "FILENAME", 0,
+ "Path to the GNU Privacy Guard executable ["GPG_DEFAULT_FILENAME"].", 2},
+ {0}
+};
+
+static char *args_doc = "";
+static char *doc = "Transparently encrypt, decrypt, and verify signatures"
+ " using GnuPG."
+ "\vFor everyday use, the commands 'encrypt', 'decrypt', and 'verify'"
+ " offer a simple and convenient frontend for this translator.";
+
+static error_t
+parse_opt (int key, char *arg, struct argp_state *state)
+{
+ error_t err;
+ switch (key)
+ {
+ case 'r':
+ err = argz_add (&recipients, &recipients_len, arg);
+ if (err)
+ error (1, err, "argz_add");
+ break;
+
+ case 'c':
+ symmetric = 1;
+ break;
+
+ case OPT_GPG_PROGRAM:
+ gpg_filename = arg;
+ break;
+
+ default:
+ return ARGP_ERR_UNKNOWN;
+ }
+ return 0;
+}
+
+int
+main (int argc, char **argv)
+{
+ error_t err;
+ mach_port_t bootstrap;
+
+ struct argp argp =
+ {
+ .options = options,
+ .parser = parse_opt,
+ .args_doc = args_doc,
+ .doc = doc,
+ };
+
+ /* Parse our command line arguments (all none of them). */
+ argp_parse (&argp, argc, argv, 0, 0, 0);
+
+ task_get_bootstrap_port (mach_task_self (), &bootstrap);
+ if (! MACH_PORT_VALID (bootstrap))
+ error (1, 0, "Must be started as a translator");
+
+ gpg_file = file_name_lookup (gpg_filename, O_EXEC, 0);
+ if (! MACH_PORT_VALID (gpg_file))
+ error (1, errno, "%s", gpg_filename);
+
+ netfs_init ();
+ chroot_init ();
+
+ /* Get our underlying node (we presume it's a directory) and use
+ that to make the root node of the filesystem. */
+ err = chroot_new_node (netfs_startup (bootstrap, O_READ),
+ sizeof (struct chroot_node), &netfs_root_node);
+ if (err)
+ error (5, err, "Cannot create root node");
+
+ err = netfs_validate_stat (netfs_root_node, 0);
+ if (err)
+ error (6, err, "Cannot stat underlying node");
+
+ netfs_root_node->nn_stat.st_mode &= ~(S_IPTRANS | S_IATRANS);
+ netfs_root_node->nn_stat.st_mode |= S_IROOT;
+ pthread_mutex_unlock (&netfs_root_node->lock);
+
+ chroot_node (netfs_root_node)->encrypt = 1;
+ chroot_node (netfs_root_node)->shadow_file = MACH_PORT_NULL;
+
+ netfs_server_loop (); /* Never returns. */
+
+ /*NOTREACHED*/
+ return 0;
+}