/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; }