diff options
author | Werner Koch <wk@gnupg.org> | 2009-10-13 21:17:24 +0200 |
---|---|---|
committer | Werner Koch <wk@gnupg.org> | 2009-10-13 21:17:24 +0200 |
commit | 536b6ab09fa3e17f955c8b55e8469f3265a1936f (patch) | |
tree | a06fba4fb448cc70de12a470d7dde7f22c3eaf8f /g13 | |
parent | Replace C99 style vararg macro which was anyway not correct. (diff) | |
download | gnupg2-536b6ab09fa3e17f955c8b55e8469f3265a1936f.tar.xz gnupg2-536b6ab09fa3e17f955c8b55e8469f3265a1936f.zip |
Keep on hacking on g13. A simple --create and --mount does now work.
A hacked up encfs is required.
Diffstat (limited to 'g13')
-rw-r--r-- | g13/Makefile.am | 2 | ||||
-rw-r--r-- | g13/backend.c | 50 | ||||
-rw-r--r-- | g13/backend.h | 10 | ||||
-rw-r--r-- | g13/be-encfs.c | 413 | ||||
-rw-r--r-- | g13/be-encfs.h | 9 | ||||
-rw-r--r-- | g13/call-gpg.c | 132 | ||||
-rw-r--r-- | g13/call-gpg.h | 2 | ||||
-rw-r--r-- | g13/create.c | 28 | ||||
-rw-r--r-- | g13/create.h | 2 | ||||
-rw-r--r-- | g13/g13.c | 127 | ||||
-rw-r--r-- | g13/keyblob.h | 5 | ||||
-rw-r--r-- | g13/mount.c | 303 | ||||
-rw-r--r-- | g13/mount.h | 29 | ||||
-rw-r--r-- | g13/runner.c | 444 | ||||
-rw-r--r-- | g13/runner.h | 68 | ||||
-rw-r--r-- | g13/utils.c | 129 | ||||
-rw-r--r-- | g13/utils.h | 15 |
17 files changed, 1750 insertions, 18 deletions
diff --git a/g13/Makefile.am b/g13/Makefile.am index 44f546eb7..2108f562a 100644 --- a/g13/Makefile.am +++ b/g13/Makefile.am @@ -31,7 +31,9 @@ g13_SOURCES = \ keyblob.h \ utils.c utils.h \ create.c create.h \ + mount.c mount.h \ call-gpg.c call-gpg.h \ + runner.c runner.h \ backend.c backend.h \ be-encfs.c be-encfs.h \ be-truecrypt.c be-truecrypt.h diff --git a/g13/backend.c b/g13/backend.c index a6f38719a..08aec324f 100644 --- a/g13/backend.c +++ b/g13/backend.c @@ -41,6 +41,22 @@ no_such_backend (int conttype) } +/* Return true if CONTTYPE is supported by us. */ +int +be_is_supported_conttype (int conttype) +{ + switch (conttype) + { + case CONTTYPE_ENCFS: + return 1; + + default: + return 0; + } +} + + + /* If the backend requires a separate file or directory for the container, return its name by computing it from FNAME which gives the g13 filename. The new file name is allocated and stored at @@ -81,3 +97,37 @@ be_create_new_keys (int conttype, membuf_t *mb) } } + +/* Dispatcher to the backend's create function. */ +gpg_error_t +be_create_container (ctrl_t ctrl, int conttype, + const char *fname, int fd, tupledesc_t tuples) +{ + (void)fd; /* Not yet used. */ + + switch (conttype) + { + case CONTTYPE_ENCFS: + return be_encfs_create_container (ctrl, fname, tuples); + + default: + return no_such_backend (conttype); + } +} + + +/* Dispatcher to the backend's mount function. */ +gpg_error_t +be_mount_container (ctrl_t ctrl, int conttype, + const char *fname, const char *mountpoint, + tupledesc_t tuples) +{ + switch (conttype) + { + case CONTTYPE_ENCFS: + return be_encfs_mount_container (ctrl, fname, mountpoint, tuples); + + default: + return no_such_backend (conttype); + } +} diff --git a/g13/backend.h b/g13/backend.h index ffd03d3f5..7cdde9e4b 100644 --- a/g13/backend.h +++ b/g13/backend.h @@ -21,12 +21,20 @@ #define G13_BACKEND_H #include "../common/membuf.h" +#include "utils.h" /* For tupledesc_t */ - +int be_is_supported_conttype (int conttype); gpg_error_t be_get_detached_name (int conttype, const char *fname, char **r_name, int *r_isdir); gpg_error_t be_create_new_keys (int conttype, membuf_t *mb); +gpg_error_t be_create_container (ctrl_t ctrl, int conttype, + const char *fname, int fd, + tupledesc_t tuples); +gpg_error_t be_mount_container (ctrl_t ctrl, int conttype, + const char *fname, const char *mountpoint, + tupledesc_t tuples); + #endif /*G13_BACKEND_H*/ diff --git a/g13/be-encfs.c b/g13/be-encfs.c index 18030b80e..0f7ec73e6 100644 --- a/g13/be-encfs.c +++ b/g13/be-encfs.c @@ -23,12 +23,301 @@ #include <string.h> #include <errno.h> #include <unistd.h> +#include <assert.h> #include "g13.h" #include "i18n.h" #include "keyblob.h" #include "be-encfs.h" +#include "runner.h" +#include "../common/exechelp.h" + +/* Command values used to run the encfs tool. */ +enum encfs_cmds + { + ENCFS_CMD_CREATE, + ENCFS_CMD_MOUNT, + ENCFS_CMD_UMOUNT + }; + + +/* An object to keep the private state of the encfs tool. It is + released by encfs_handler_cleanup. */ +struct encfs_parm_s +{ + enum encfs_cmds cmd; /* The current command. */ + tupledesc_t tuples; /* NULL or the tuples object. */ + char *mountpoint; /* The mountpoint. */ +}; +typedef struct encfs_parm_s *encfs_parm_t; + + +static gpg_error_t +send_cmd_bin (runner_t runner, const void *data, size_t datalen) +{ + return runner_send_line (runner, data, datalen); +} + + +static gpg_error_t +send_cmd (runner_t runner, const char *string) +{ + log_debug ("sending command -->%s<--\n", string); + return send_cmd_bin (runner, string, strlen (string)); +} + + + +static void +run_umount_helper (const char *mountpoint) +{ + gpg_error_t err; + const char pgmname[] = "/usr/bin/fusermount"; + const char *args[3]; + + args[0] = "-u"; + args[1] = mountpoint; + args[2] = NULL; + + err = gnupg_spawn_process_detached (pgmname, args, NULL); + if (err) + log_error ("failed to run `%s': %s\n", + pgmname, gpg_strerror (err)); +} + + +/* Handle one line of the encfs tool's output. This function is + allowed to modify the content of BUFFER. */ +static gpg_error_t +handle_status_line (runner_t runner, const char *line, + enum encfs_cmds cmd, tupledesc_t tuples) +{ + gpg_error_t err; + + /* Check that encfs understands our new options. */ + if (!strncmp (line, "$STATUS$", 8)) + { + for (line +=8; *line && spacep (line); line++) + ; + log_info ("got status `%s'\n", line); + if (!strcmp (line, "fuse_main_start")) + { + /* Send a special error code back to let the caller know + that everything has been setup by encfs. */ + err = gpg_error (GPG_ERR_UNFINISHED); + } + else + err = 0; + } + else if (!strncmp (line, "$PROMPT$", 8)) + { + for (line +=8; *line && spacep (line); line++) + ; + log_info ("got prompt `%s'\n", line); + if (!strcmp (line, "create_root_dir")) + err = send_cmd (runner, cmd == ENCFS_CMD_CREATE? "y":"n"); + else if (!strcmp (line, "create_mount_point")) + err = send_cmd (runner, "y"); + else if (!strcmp (line, "passwd") + || !strcmp (line, "new_passwd")) + { + if (tuples) + { + size_t n; + const void *value; + + value = find_tuple (tuples, KEYBLOB_TAG_ENCKEY, &n); + if (!value) + err = gpg_error (GPG_ERR_INV_SESSION_KEY); + else if ((err = send_cmd_bin (runner, value, n))) + { + if (gpg_err_code (err) == GPG_ERR_BUG + && gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT) + err = gpg_error (GPG_ERR_INV_SESSION_KEY); + } + } + else + err = gpg_error (GPG_ERR_NO_DATA); + } + else + err = send_cmd (runner, ""); /* Default to send an empty line. */ + } + else if (strstr (line, "encfs: unrecognized option '")) + err = gpg_error (GPG_ERR_INV_ENGINE); + else + err = 0; + + return err; +} + + +/* The main processing function as used by the runner. */ +static gpg_error_t +encfs_handler (void *opaque, runner_t runner, const char *status_line) +{ + encfs_parm_t parm = opaque; + gpg_error_t err; + + if (!parm || !runner) + return gpg_error (GPG_ERR_BUG); + if (!status_line) + { + /* Runner requested internal flushing - nothing to do here. */ + return 0; + } + + err = handle_status_line (runner, status_line, parm->cmd, parm->tuples); + if (gpg_err_code (err) == GPG_ERR_UNFINISHED + && gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT) + { + err = 0; + /* No more need for the tuples. */ + destroy_tupledesc (parm->tuples); + parm->tuples = NULL; + + if (parm->cmd == ENCFS_CMD_CREATE) + { + /* The encfs tool keeps on running after creation of the + container. We don't want that and thus need to stop the + encfs process. */ + run_umount_helper (parm->mountpoint); + /* In case the umount helper does not work we try to kill + the engine. FIXME: We should figure out how to make + fusermount work. */ + runner_cancel (runner); + } + } + + return err; +} + + +/* Called by the runner to cleanup the private data. */ +static void +encfs_handler_cleanup (void *opaque) +{ + encfs_parm_t parm = opaque; + + if (!parm) + return; + + destroy_tupledesc (parm->tuples); + xfree (parm->mountpoint); + xfree (parm); +} + + +/* Run the encfs tool. */ +static gpg_error_t +run_encfs_tool (ctrl_t ctrl, enum encfs_cmds cmd, + const char *rawdir, const char *mountpoint, tupledesc_t tuples) +{ + gpg_error_t err; + encfs_parm_t parm; + runner_t runner = NULL; + int outbound[2] = { -1, -1 }; + int inbound[2] = { -1, -1 }; + const char *pgmname; + const char *argv[10]; + pid_t pid = (pid_t)(-1); + int idx; + + (void)ctrl; + + parm = xtrycalloc (1, sizeof *parm); + if (!parm) + { + err = gpg_error_from_syserror (); + goto leave; + } + parm->cmd = cmd; + parm->tuples = ref_tupledesc (tuples); + parm->mountpoint = xtrystrdup (mountpoint); + if (!parm->mountpoint) + { + err = gpg_error_from_syserror (); + goto leave; + } + + { + static int namecounter; + char buffer[50]; + + snprintf (buffer, sizeof buffer, "encfs-%d", ++namecounter); + err = runner_new (&runner, buffer); + if (err) + goto leave; + } + + err = gnupg_create_inbound_pipe (inbound); + if (!err) + err = gnupg_create_outbound_pipe (outbound); + if (err) + { + log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); + goto leave; + } + + pgmname = "/usr/bin/encfs"; + idx = 0; + argv[idx++] = "-f"; + argv[idx++] = "-v"; + argv[idx++] = "--stdinpass"; + argv[idx++] = "--annotate"; + argv[idx++] = rawdir; + argv[idx++] = mountpoint; + argv[idx++] = NULL; + assert (idx <= DIM (argv)); + + err = gnupg_spawn_process_fd (pgmname, argv, + outbound[0], -1, inbound[1], &pid); + if (err) + { + log_error ("error spawning `%s': %s\n", pgmname, gpg_strerror (err)); + goto leave; + } + close (outbound[0]); outbound[0] = -1; + close ( inbound[1]); inbound[1] = -1; + + runner_set_fds (runner, inbound[0], outbound[1]); + inbound[0] = -1; /* Now owned by RUNNER. */ + outbound[1] = -1; /* Now owned by RUNNER. */ + + runner_set_handler (runner, encfs_handler, encfs_handler_cleanup, parm); + parm = NULL; /* Now owned by RUNNER. */ + + runner_set_pid (runner, pid); + pid = (pid_t)(-1); /* The process is now owned by RUNNER. */ + + err = runner_spawn (runner); + if (err) + goto leave; + + log_info ("running `%s' in the background\n", pgmname); + + leave: + if (inbound[0] != -1) + close (inbound[0]); + if (inbound[1] != -1) + close (inbound[1]); + if (outbound[0] != -1) + close (outbound[0]); + if (outbound[1] != -1) + close (outbound[1]); + if (pid != (pid_t)(-1)) + { + gnupg_wait_process (pgmname, pid, NULL); + } + runner_release (runner); + encfs_handler_cleanup (parm); + return err; +} + + + + + /* See be_get_detached_name for a description. Note that the dispatcher code makes sure that NULL is stored at R_NAME before calling us. */ @@ -49,10 +338,134 @@ be_encfs_get_detached_name (const char *fname, char **r_name, int *r_isdir) } +/* Create a new session key and append it as a tuple to the memory + buffer MB. + + The EncFS daemon takes a passphrase from stdin and internally + mangles it by means of some KDF from OpenSSL. We want to store a + binary key but we need to make sure that certain characters are not + used because the EncFS utility reads it from stdin and obviously + acts on some of the characters. This we replace CR (in case of an + MSDOS version of EncFS), LF (the delimiter used by EncFS) and Nul + (because it is unlikely to work). We use 32 bytes (256 bit) + because that is sufficient for the largest cipher (AES-256) and in + addition gives enough margin for a possible entropy degradation by + the KDF. */ gpg_error_t be_encfs_create_new_keys (membuf_t *mb) { + char *buffer; + int i, j; + + /* Allocate a buffer of 32 bytes plus 8 spare bytes we may need to + replace the unwanted values. */ + buffer = xtrymalloc_secure (32+8); + if (!buffer) + return gpg_error_from_syserror (); + + /* Randomize the buffer. STRONG random should be enough as it is a + good compromise between security and performance. The + anticipated usage of this tool is the quite often creation of new + containers and thus this should not deplete the system's entropy + tool too much. */ + gcry_randomize (buffer, 32+8, GCRY_STRONG_RANDOM); + for (i=j=0; i < 32; i++) + { + if (buffer[i] == '\r' || buffer[i] == '\n' || buffer[i] == 0 ) + { + /* Replace. */ + if (j == 8) + { + /* Need to get more random. */ + gcry_randomize (buffer+32, 8, GCRY_STRONG_RANDOM); + j = 0; + } + buffer[i] = buffer[32+j]; + j++; + } + } + + /* Store the key. */ + append_tuple (mb, KEYBLOB_TAG_ENCKEY, buffer, 32); + + /* Free the temporary buffer. */ + wipememory (buffer, 32+8); /* A failsafe extra wiping. */ + xfree (buffer); + return 0; } +/* Create the container described by the filename FNAME and the keyblob + information in TUPLES. */ +gpg_error_t +be_encfs_create_container (ctrl_t ctrl, const char *fname, tupledesc_t tuples) +{ + gpg_error_t err; + int dummy; + char *containername = NULL; + char *mountpoint = NULL; + + err = be_encfs_get_detached_name (fname, &containername, &dummy); + if (err) + goto leave; + + mountpoint = xtrystrdup ("/tmp/.#g13_XXXXXX"); + if (!mountpoint) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (!mkdtemp (mountpoint)) + { + err = gpg_error_from_syserror (); + log_error (_("can't create directory `%s': %s\n"), + "/tmp/g13-XXXXXX", gpg_strerror (err)); + goto leave; + } + + err = run_encfs_tool (ctrl, ENCFS_CMD_CREATE, containername, mountpoint, + tuples); + + /* In any case remove the temporary mount point. */ + if (rmdir (mountpoint)) + log_error ("error removing temporary mount point `%s': %s\n", + mountpoint, gpg_strerror (gpg_error_from_syserror ())); + + + leave: + xfree (containername); + xfree (mountpoint); + return err; +} + + +/* Mount the container described by the filename FNAME and the keyblob + information in TUPLES. */ +gpg_error_t +be_encfs_mount_container (ctrl_t ctrl, + const char *fname, const char *mountpoint, + tupledesc_t tuples) +{ + gpg_error_t err; + int dummy; + char *containername = NULL; + + if (!mountpoint) + { + log_error ("the encfs backend requires an explicit mountpoint\n"); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + + err = be_encfs_get_detached_name (fname, &containername, &dummy); + if (err) + goto leave; + + err = run_encfs_tool (ctrl, ENCFS_CMD_MOUNT, containername, mountpoint, + tuples); + + leave: + xfree (containername); + return err; +} diff --git a/g13/be-encfs.h b/g13/be-encfs.h index 061385345..c6c8396c5 100644 --- a/g13/be-encfs.h +++ b/g13/be-encfs.h @@ -26,6 +26,15 @@ gpg_error_t be_encfs_get_detached_name (const char *fname, char **r_name, int *r_isdir); gpg_error_t be_encfs_create_new_keys (membuf_t *mb); +gpg_error_t be_encfs_create_container (ctrl_t ctrl, + const char *fname, + tupledesc_t tuples); + +gpg_error_t be_encfs_mount_container (ctrl_t ctrl, + const char *fname, + const char *mountpoint, + tupledesc_t tuples); + #endif /*G13_BE_ENCFS_H*/ diff --git a/g13/call-gpg.c b/g13/call-gpg.c index 2399058b0..dd519021e 100644 --- a/g13/call-gpg.c +++ b/g13/call-gpg.c @@ -43,7 +43,7 @@ start_gpg (ctrl_t ctrl, int input_fd, int output_fd, assuan_context_t *r_ctx) gpg_error_t err; assuan_context_t ctx = NULL; const char *pgmname; - const char *argv[6]; + const char *argv[7]; int no_close_list[5]; int i; char line[ASSUAN_LINELENGTH]; @@ -464,3 +464,133 @@ gpg_encrypt_blob (ctrl_t ctrl, const void *plain, size_t plainlen, } + +/* Call GPG to decrypt a block of data. + + + */ +gpg_error_t +gpg_decrypt_blob (ctrl_t ctrl, const void *ciph, size_t ciphlen, + void **r_plain, size_t *r_plainlen) +{ + gpg_error_t err; + assuan_context_t ctx; + int outbound_fds[2] = { -1, -1 }; + int inbound_fds[2] = { -1, -1 }; + pth_t writer_tid = NULL; + pth_t reader_tid = NULL; + gpg_error_t writer_err, reader_err; + membuf_t reader_mb; + + *r_plain = NULL; + *r_plainlen = 0; + + /* Init the memory buffer to receive the encrypted stuff. */ + init_membuf_secure (&reader_mb, 1024); + + /* Create two pipes. */ + err = gnupg_create_outbound_pipe (outbound_fds); + if (!err) + err = gnupg_create_inbound_pipe (inbound_fds); + if (err) + { + log_error (_("error creating a pipe: %s\n"), gpg_strerror (err)); + goto leave; + } + + /* Start GPG and send the INPUT and OUTPUT commands. */ + err = start_gpg (ctrl, outbound_fds[0], inbound_fds[1], &ctx); + if (err) + goto leave; + close (outbound_fds[0]); outbound_fds[0] = -1; + close (inbound_fds[1]); inbound_fds[1] = -1; + + /* Start a writer thread to feed the INPUT command of the server. */ + err = start_writer (outbound_fds[1], ciph, ciphlen, + &writer_tid, &writer_err); + if (err) + return err; + outbound_fds[1] = -1; /* The thread owns the FD now. */ + + /* Start a reader thread to eat from the OUTPUT command of the + server. */ + err = start_reader (inbound_fds[0], &reader_mb, + &reader_tid, &reader_err); + if (err) + return err; + outbound_fds[0] = -1; /* The thread owns the FD now. */ + + /* Run the decryption. */ + err = assuan_transact (ctx, "DECRYPT", NULL, NULL, NULL, NULL, NULL, NULL); + if (err) + { + log_error ("the engine's DECRYPT command failed: %s <%s>\n", + gpg_strerror (err), gpg_strsource (err)); + goto leave; + } + + /* Wait for reader and return the data. */ + if (!pth_join (reader_tid, NULL)) + { + err = gpg_error_from_syserror (); + log_error ("waiting for reader thread failed: %s\n", gpg_strerror (err)); + goto leave; + } + reader_tid = NULL; + if (reader_err) + { + err = reader_err; + log_error ("read error in reader thread: %s\n", gpg_strerror (err)); + goto leave; + } + + /* Wait for the writer to catch a writer error. */ + if (!pth_join (writer_tid, NULL)) + { + err = gpg_error_from_syserror (); + log_error ("waiting for writer thread failed: %s\n", gpg_strerror (err)); + goto leave; + } + writer_tid = NULL; + if (writer_err) + { + err = writer_err; + log_error ("write error in writer thread: %s\n", gpg_strerror (err)); + goto leave; + } + + /* Return the data. */ + *r_plain = get_membuf (&reader_mb, r_plainlen); + if (!*r_plain) + { + err = gpg_error_from_syserror (); + log_error ("error while storing the data in the reader thread: %s\n", + gpg_strerror (err)); + goto leave; + } + + leave: + if (reader_tid) + { + pth_cancel (reader_tid); + pth_join (reader_tid, NULL); + } + if (writer_tid) + { + pth_cancel (writer_tid); + pth_join (writer_tid, NULL); + } + if (outbound_fds[0] != -1) + close (outbound_fds[0]); + if (outbound_fds[1] != -1) + close (outbound_fds[1]); + if (inbound_fds[0] != -1) + close (inbound_fds[0]); + if (inbound_fds[1] != -1) + close (inbound_fds[1]); + release_gpg (ctx); + xfree (get_membuf (&reader_mb, NULL)); + return err; +} + + diff --git a/g13/call-gpg.h b/g13/call-gpg.h index 3e801be3b..ffc5f29b5 100644 --- a/g13/call-gpg.h +++ b/g13/call-gpg.h @@ -23,6 +23,8 @@ gpg_error_t gpg_encrypt_blob (ctrl_t ctrl, const void *plain, size_t plainlen, void **r_ciph, size_t *r_ciphlen); +gpg_error_t gpg_decrypt_blob (ctrl_t ctrl, const void *ciph, size_t ciphlen, + void **r_plain, size_t *r_plainlen); diff --git a/g13/create.c b/g13/create.c index 0c6735b80..7f3a349b2 100644 --- a/g13/create.c +++ b/g13/create.c @@ -79,9 +79,9 @@ create_new_keyblob (ctrl_t ctrl, int is_detached, if (err) goto leave; + /* Just for testing. */ append_tuple (&mb, KEYBLOB_TAG_FILLER, "filler", 6); - *r_blob = get_membuf (&mb, r_bloblen); if (!*r_blob) { @@ -122,7 +122,7 @@ encrypt_keyblob (ctrl_t ctrl, void *keyblob, size_t keybloblen, appropriate header. This fucntion is called with a lock file in place and after checking that the filename does not exists. */ static gpg_error_t -write_keyblob (ctrl_t ctrl, const char *filename, +write_keyblob (const char *filename, const void *keyblob, size_t keybloblen) { gpg_error_t err; @@ -152,7 +152,7 @@ write_keyblob (ctrl_t ctrl, const char *filename, packet[4] = 0; packet[5] = 26; memcpy (packet+6, "GnuPG/G13", 10); /* Packet subtype. */ - packet[16] = 1; /* G13 packet format. */ + packet[16] = 1; /* G13 packet format version. */ packet[17] = 0; /* Reserved. */ packet[18] = 0; /* Reserved. */ packet[19] = 0; /* OS Flag. */ @@ -202,7 +202,7 @@ write_keyblob (ctrl_t ctrl, const char *filename, return err; } - return err; + return 0; writeerr: @@ -220,7 +220,7 @@ write_keyblob (ctrl_t ctrl, const char *filename, using the current settings. If the file already exists an error is returned. */ gpg_error_t -create_new_container (ctrl_t ctrl, const char *filename) +g13_create_container (ctrl_t ctrl, const char *filename) { gpg_error_t err; dotlock_t lock; @@ -230,6 +230,7 @@ create_new_container (ctrl_t ctrl, const char *filename) size_t enckeybloblen; char *detachedname = NULL; int detachedisdir; + tupledesc_t tuples = NULL; /* A quick check to see that no container with that name already exists. */ @@ -286,17 +287,28 @@ create_new_container (ctrl_t ctrl, const char *filename) &enckeyblob, &enckeybloblen); if (err) goto leave; + + /* Put a copy of the keyblob into a tuple structure. */ + err = create_tupledesc (&tuples, keyblob, keybloblen); + if (err) + goto leave; + keyblob = NULL; + /* if (opt.verbose) */ + /* dump_keyblob (tuples); */ /* Write out the header, the encrypted keyblob and some padding. */ - err = write_keyblob (ctrl, filename, enckeyblob, enckeybloblen); + err = write_keyblob (filename, enckeyblob, enckeybloblen); if (err) goto leave; - /* Create and append the container. */ - + /* Create and append the container. FIXME: We should pass the + estream object in addition to the filename, so that the backend + can append the container to the g13 file. */ + err = be_create_container (ctrl, ctrl->conttype, filename, -1, tuples); leave: + destroy_tupledesc (tuples); xfree (detachedname); xfree (enckeyblob); xfree (keyblob); diff --git a/g13/create.h b/g13/create.h index afe616b69..d533c0852 100644 --- a/g13/create.h +++ b/g13/create.h @@ -20,7 +20,7 @@ #ifndef G13_CREATE_H #define G13_CREATE_H -gpg_error_t create_new_container (ctrl_t ctrl, const char *filename); +gpg_error_t g13_create_container (ctrl_t ctrl, const char *filename); #endif /*G13_CREATE_H*/ @@ -35,8 +35,10 @@ #include "i18n.h" #include "sysutils.h" #include "gc-opt-flags.h" -#include "create.h" #include "keyblob.h" +#include "./runner.h" +#include "./create.h" +#include "./mount.h" enum cmd_and_opt_values { @@ -84,6 +86,7 @@ enum cmd_and_opt_values { oHomedir, oWithColons, oDryRun, + oNoDetach, oRecipient, @@ -111,6 +114,7 @@ static ARGPARSE_OPTS opts[] = { ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), ARGPARSE_s_n (oNoTTY, "no-tty", N_("don't use the terminal at all")), + ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")), ARGPARSE_s_s (oLogFile, "log-file", N_("|FILE|write log output to FILE")), ARGPARSE_s_n (oNoLogFile, "no-log-file", "@"), ARGPARSE_s_i (oLoggerFD, "logger-fd", "@"), @@ -329,6 +333,7 @@ main ( int argc, char **argv) int nogreeting = 0; int debug_wait = 0; int use_random_seed = 1; + int nodetach = 0; int nokeysetup = 0; enum cmd_and_opt_values cmd = 0; struct server_control_s ctrl; @@ -499,6 +504,8 @@ main ( int argc, char **argv) case oAuditLog: auditlog = pargs.r.ret_str; break; + case oNoDetach: nodetach = 1; break; + case oDebug: debug_value |= pargs.r.ret_ulong; break; case oDebugAll: debug_value = ~0; break; case oDebugNone: debug_value = 0; break; @@ -677,16 +684,47 @@ main ( int argc, char **argv) { if (argc != 1) wrong_args ("--create filename"); - err = create_new_container (&ctrl, argv[0]); + err = g13_create_container (&ctrl, argv[0]); if (err) log_error ("error creating a new container: %s <%s>\n", gpg_strerror (err), gpg_strsource (err)); + else + { + unsigned int n; + + while ((n = runner_get_threads ())) + { + log_info ("number of running threads: %u\n", n); + pth_sleep (5); + } + } + } + break; + + case aMount: /* Mount a container. */ + { + if (argc != 1 && argc != 2 ) + wrong_args ("--mount filename [mountpoint]"); + err = g13_mount_container (&ctrl, argv[0], argc == 2?argv[1]:NULL); + if (err) + log_error ("error mounting container `%s': %s <%s>\n", + *argv, gpg_strerror (err), gpg_strsource (err)); + else + { + unsigned int n; + + while ((n = runner_get_threads ())) + { + log_info ("number of running threads: %u\n", n); + pth_sleep (5); + } + } } break; default: - log_error (_("invalid command (there is no implicit command)\n")); - break; + log_error (_("invalid command (there is no implicit command)\n")); + break; } /* Print the audit result if needed. */ @@ -735,3 +773,84 @@ g13_init_default_ctrl (struct server_control_s *ctrl) } +/* static void */ +/* daemonize (int nodetach) */ +/* { */ +/* gnupg_fd_t fd; */ +/* gnupg_fd_t fd_ssh; */ +/* pid_t pid; */ + +/* fflush (NULL); */ +/* #ifdef HAVE_W32_SYSTEM */ +/* pid = getpid (); */ +/* #else /\*!HAVE_W32_SYSTEM*\/ */ +/* pid = fork (); */ +/* if (pid == (pid_t)-1) */ +/* { */ +/* log_fatal ("fork failed: %s\n", strerror (errno) ); */ +/* g13_exit (1); */ +/* } */ +/* else if (pid) /\* We are the parent *\/ */ +/* { */ +/* /\* We need to clwanup our resources. An gcry_atfork might be */ +/* needed. *\/ */ +/* exit (0); */ +/* /\*NOTREACHED*\/ */ +/* } /\* End parent *\/ */ + +/* /\* */ +/* This is the child */ +/* *\/ */ + +/* /\* Detach from tty and put process into a new session *\/ */ +/* if (!nodetach ) */ +/* { */ +/* int i; */ +/* unsigned int oldflags; */ + +/* /\* Close stdin, stdout and stderr unless it is the log stream *\/ */ +/* for (i=0; i <= 2; i++) */ +/* { */ +/* if (!log_test_fd (i) && i != fd ) */ +/* { */ +/* if ( ! close (i) */ +/* && open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1) */ +/* { */ +/* log_error ("failed to open `%s': %s\n", */ +/* "/dev/null", strerror (errno)); */ +/* cleanup (); */ +/* exit (1); */ +/* } */ +/* } */ +/* } */ +/* if (setsid() == -1) */ +/* { */ +/* log_error ("setsid() failed: %s\n", strerror(errno) ); */ +/* cleanup (); */ +/* exit (1); */ +/* } */ + +/* log_get_prefix (&oldflags); */ +/* log_set_prefix (NULL, oldflags | JNLIB_LOG_RUN_DETACHED); */ +/* opt.running_detached = 1; */ +/* } */ + +/* if (chdir("/")) */ +/* { */ +/* log_error ("chdir to / failed: %s\n", strerror (errno)); */ +/* exit (1); */ +/* } */ + +/* { */ +/* struct sigaction sa; */ + +/* sa.sa_handler = SIG_IGN; */ +/* sigemptyset (&sa.sa_mask); */ +/* sa.sa_flags = 0; */ +/* sigaction (SIGPIPE, &sa, NULL); */ +/* } */ +/* #endif /\*!HAVE_W32_SYSTEM*\/ */ + +/* log_info ("%s %s started\n", strusage(11), strusage(13) ); */ +/* handle_something (fd, opt.ssh_support ? fd_ssh : GNUPG_INVALID_FD); */ +/* } */ diff --git a/g13/keyblob.h b/g13/keyblob.h index b52919e0c..a7701005d 100644 --- a/g13/keyblob.h +++ b/g13/keyblob.h @@ -63,8 +63,9 @@ #define KEYBLOB_TAG_BLOBVERSION 0 /* This tag is used to describe the version of the keyblob. It must - be the first tag in a keyblob. Its value is a single byte giving - the blob version. The current version is 1. */ + be the first tag in a keyblob and may only occur once. Its value + is a single byte giving the blob version. The only defined version + is 1. */ #define KEYBLOB_TAG_CONTTYPE 1 /* This tag gives the type of the container. The value is a two byte diff --git a/g13/mount.c b/g13/mount.c new file mode 100644 index 000000000..85851e9a8 --- /dev/null +++ b/g13/mount.c @@ -0,0 +1,303 @@ +/* mount.c - Mount a crypto container + * Copyright (C) 2009 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG 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 3 of the License, or + * (at your option) any later version. + * + * GnuPG 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, see <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <sys/stat.h> +#include <assert.h> + +#include "g13.h" +#include "i18n.h" +#include "mount.h" + +#include "keyblob.h" +#include "backend.h" +#include "utils.h" +#include "call-gpg.h" +#include "estream.h" + + +/* Parse the header prefix and return the length of the entire header. */ +static gpg_error_t +parse_header (const char *filename, + const unsigned char *packet, size_t packetlen, + size_t *r_headerlen) +{ + unsigned int len; + + if (packetlen != 32) + return gpg_error (GPG_ERR_BUG); + + len = ((packet[2] << 24) | (packet[3] << 16) + | (packet[4] << 8) | packet[5]); + if (packet[0] != (0xc0|61) || len < 26 + || memcmp (packet+6, "GnuPG/G13", 10)) + { + log_error ("file `%s' is not valid container\n", filename); + return gpg_error (GPG_ERR_INV_OBJ); + } + if (packet[16] != 1) + { + log_error ("unknown version %u of container `%s'\n", + (unsigned int)packet[16], filename); + return gpg_error (GPG_ERR_INV_OBJ); + } + if (packet[17] || packet[18] + || packet[26] || packet[27] || packet[28] || packet[29] + || packet[30] || packet[31]) + log_info ("WARNING: unknown meta information in `%s'\n", filename); + if (packet[19]) + log_info ("WARNING: OS flag is not supported in `%s'\n", filename); + if (packet[24] != 1 || packet[25] != 0) + { + log_error ("meta data copies in `%s' are not supported\n", filename); + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); + } + + len = ((packet[20] << 24) | (packet[21] << 16) + | (packet[22] << 8) | packet[23]); + + /* Do a basic sanity check on the length. */ + if (len < 32 || len > 1024*1024) + { + log_error ("bad length given in container `%s'\n", filename); + return gpg_error (GPG_ERR_INV_OBJ); + } + + *r_headerlen = len; + return 0; +} + + + +/* Read the keyblob at FILENAME. The caller should have acquired a + lockfile and checked that the file exists. */ +static gpg_error_t +read_keyblob (const char *filename, + void **r_enckeyblob, size_t *r_enckeybloblen) +{ + gpg_error_t err; + estream_t fp; + unsigned char packet[32]; + size_t headerlen, msglen; + void *msg = NULL; + + *r_enckeyblob = NULL; + *r_enckeybloblen = 0; + + fp = es_fopen (filename, "rb"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error ("error reading `%s': %s\n", + filename, gpg_strerror (err)); + return err; + } + + /* Read the header. It is defined as 32 bytes thus we read it in one go. */ + if (es_fread (packet, 32, 1, fp) != 1) + { + err = gpg_error_from_syserror (); + log_error ("error reading the header of `%s': %s\n", + filename, gpg_strerror (err)); + goto leave; + } + + err = parse_header (filename, packet, 32, &headerlen); + if (err) + goto leave; + + if (opt.verbose) + log_info ("header length of `%s' is %zu\n", filename, headerlen); + + /* Read everything including the padding. We should eventually do a + regular OpenPGP parsing to detect the padding packet and pass + only the actual used OpenPGP data to the engine. This is in + particular required when supporting CMS which will be + encapsulated in an OpenPGP packet. */ + assert (headerlen >= 32); + msglen = headerlen - 32; + if (!msglen) + { + err = gpg_error (GPG_ERR_NO_DATA); + goto leave; + } + msg = xtrymalloc (msglen); + if (!msglen) + { + err = gpg_error_from_syserror (); + goto leave; + } + if (es_fread (msg, msglen, 1, fp) != 1) + { + err = gpg_error_from_syserror (); + log_error ("error reading keyblob of `%s': %s\n", + filename, gpg_strerror (err)); + goto leave; + } + + *r_enckeyblob = msg; + msg = NULL; + *r_enckeybloblen = msglen; + + leave: + xfree (msg); + es_fclose (fp); + + return err; +} + + + + +/* Decrypt the keyblob (ENCKEYBLOB,ENCKEYBLOBLEN) and store the result at + (R_KEYBLOB, R_KEYBLOBLEN). Returns 0 on success or an error code. + On error R_KEYBLOB is set to NULL. */ +static gpg_error_t +decrypt_keyblob (ctrl_t ctrl, const void *enckeyblob, size_t enckeybloblen, + void **r_keyblob, size_t *r_keybloblen) +{ + gpg_error_t err; + + /* FIXME: For now we only implement OpenPGP. */ + err = gpg_decrypt_blob (ctrl, enckeyblob, enckeybloblen, + r_keyblob, r_keybloblen); + + return err; +} + + +static void +dump_keyblob (tupledesc_t tuples) +{ + size_t n; + unsigned int tag; + const void *value; + + log_info ("keyblob dump:\n"); + tag = KEYBLOB_TAG_BLOBVERSION; + value = find_tuple (tuples, tag, &n); + while (value) + { + log_info (" tag: %-5u len: %-2u value: ", tag, (unsigned int)n); + if (tag == KEYBLOB_TAG_ENCKEY + || tag == KEYBLOB_TAG_MACKEY) + log_printf ("[confidential]\n"); + else if (!n) + log_printf ("[none]\n"); + else + log_printhex ("", value, n); + value = next_tuple (tuples, &tag, &n); + } +} + + + +/* Mount the container with name FILENAME at MOUNTPOINT. */ +gpg_error_t +g13_mount_container (ctrl_t ctrl, const char *filename, const char *mountpoint) +{ + gpg_error_t err; + dotlock_t lock; + void *enckeyblob = NULL; + size_t enckeybloblen; + void *keyblob = NULL; + size_t keybloblen; + tupledesc_t tuples = NULL; + size_t n; + const unsigned char *value; + int conttype; + + /* A quick check to see whether the container exists. */ + if (access (filename, R_OK)) + return gpg_error_from_syserror (); + + /* Try to take a lock. */ + lock = create_dotlock (filename); + if (!lock) + return gpg_error_from_syserror (); + + if (make_dotlock (lock, 0)) + { + err = gpg_error_from_syserror (); + goto leave; + } + else + err = 0; + + /* Check again that the file exists. */ + { + struct stat sb; + + if (stat (filename, &sb)) + { + err = gpg_error_from_syserror (); + goto leave; + } + } + + /* Read the encrypted keyblob. */ + err = read_keyblob (filename, &enckeyblob, &enckeybloblen); + if (err) + goto leave; + + /* Decrypt that keyblob and store it in a tuple descriptor. */ + err = decrypt_keyblob (ctrl, enckeyblob, enckeybloblen, + &keyblob, &keybloblen); + if (err) + goto leave; + xfree (enckeyblob); + enckeyblob = NULL; + + err = create_tupledesc (&tuples, keyblob, keybloblen); + if (!err) + keyblob = NULL; + else + { + if (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED) + log_error ("unknown keyblob version\n"); + goto leave; + } + if (opt.verbose) + dump_keyblob (tuples); + + value = find_tuple (tuples, KEYBLOB_TAG_CONTTYPE, &n); + if (!value || n != 2) + conttype = 0; + else + conttype = (value[0] << 8 | value[1]); + if (!be_is_supported_conttype (conttype)) + { + log_error ("content type %d is not supported\n", conttype); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } + err = be_mount_container (ctrl, conttype, filename, mountpoint, tuples); + + leave: + destroy_tupledesc (tuples); + xfree (keyblob); + xfree (enckeyblob); + destroy_dotlock (lock); + return err; +} diff --git a/g13/mount.h b/g13/mount.h new file mode 100644 index 000000000..03b8264c9 --- /dev/null +++ b/g13/mount.h @@ -0,0 +1,29 @@ +/* mmount.h - Defs to mount a crypto container + * Copyright (C) 2009 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG 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 3 of the License, or + * (at your option) any later version. + * + * GnuPG 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef G13_MOUNT_H +#define G13_MOUNT_H + +gpg_error_t g13_mount_container (ctrl_t ctrl, + const char *filename, + const char *mountpoint); + + +#endif /*G13_MOUNT_H*/ + diff --git a/g13/runner.c b/g13/runner.c new file mode 100644 index 000000000..d88b69b98 --- /dev/null +++ b/g13/runner.c @@ -0,0 +1,444 @@ +/* runner.c - Run and watch the backend engines + * Copyright (C) 2009 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG 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 3 of the License, or + * (at your option) any later version. + * + * GnuPG 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, see <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <assert.h> +#include <pth.h> + +#include "g13.h" +#include "i18n.h" +#include "keyblob.h" +#include "runner.h" +#include "../common/exechelp.h" + + +/* The runner object. */ +struct runner_s +{ + char *name; /* The name of this runner. */ + + int spawned; /* True if runner_spawn has been called. */ + pth_t threadid; /* The TID of the runner thread. */ + + int cancel_flag; /* If set the thread should terminate itself. */ + + /* We use a reference counter to know when it is safe to remove the + object. Lackiong an explicit ref fucntion this counter will take + only these two values: + + 1 = Thread not running or only the thread is still running. + 2 = Thread is running and someone is holding a reference. */ + int refcount; + + pid_t pid; /* PID of the backend's process (the engine). */ + int in_fd; /* File descriptors to read from the engine. */ + int out_fd; /* File descriptors to write to the engine. */ + engine_handler_fnc_t handler; /* The handler functions. */ + engine_handler_cleanup_fnc_t handler_cleanup; + void *handler_data; /* Private data of HANDLER and HANDLER_CLEANUP. */ + + /* Instead of IN_FD we use an estream. Note that the runner thread + may close the stream and set status_fp to NULL at any time. Thus + it won't be a good idea to use it while the runner thread is + running. */ + estream_t status_fp; +}; + + +/* Avariabale to track the number of active runner threads. */ +static unsigned int thread_count; + + + +/* Write NBYTES of BUF to file descriptor FD. */ +static int +writen (int fd, const void *buf, size_t nbytes) +{ + size_t nleft = nbytes; + int nwritten; + + while (nleft > 0) + { + nwritten = pth_write (fd, buf, nleft); + if (nwritten < 0) + { + if (errno == EINTR) + nwritten = 0; + else + return -1; + } + nleft -= nwritten; + buf = (const char*)buf + nwritten; + } + + return 0; +} + + +static int +check_already_spawned (runner_t runner, const char *funcname) +{ + if (runner->spawned) + { + log_error ("BUG: runner already spawned - ignoring call to %s\n", + funcname); + return 1; + } + else + return 0; +} + + +/* Return the number of active threads. */ +unsigned int +runner_get_threads (void) +{ + return thread_count; +} + + +/* The public release function. */ +void +runner_release (runner_t runner) +{ + if (!runner) + return; + + if (!--runner->refcount) + return; + + es_fclose (runner->status_fp); + if (runner->in_fd != -1) + close (runner->in_fd); + if (runner->out_fd != -1) + close (runner->out_fd); + + /* Fixme: close the process. */ + + /* Tell the engine to release its data. */ + if (runner->handler_cleanup) + runner->handler_cleanup (runner->handler_data); + + if (runner->pid != (pid_t)(-1)) + { + /* The process has not been cleaned up - do it now. */ + gnupg_kill_process (runner->pid); + /* (Actually we should use the program name and not the + arbitrary NAME of the runner object. However it does not + matter because that information is only used for + diagnostics.) */ + gnupg_wait_process (runner->name, runner->pid, NULL); + } + + xfree (runner->name); + xfree (runner); +} + + +/* Create a new runner context. On success a new runner object is + stored at R_RUNNER. On failure NULL is stored at this address and + an error code returned. */ +gpg_error_t +runner_new (runner_t *r_runner, const char *name) +{ + runner_t runner; + + *r_runner = NULL; + + runner = xtrycalloc (1, sizeof *runner); + if (!runner) + return gpg_error_from_syserror (); + runner->name = xtrystrdup (name? name: "[unknown]"); + if (!runner->name) + { + xfree (runner); + return gpg_error_from_syserror (); + } + runner->refcount = 1; + runner->pid = (pid_t)(-1); + runner->in_fd = -1; + runner->out_fd = -1; + + + *r_runner = runner; + return 0; +} + + +/* A runner usually maintaines two file descriptors to control the + backend engine. This function is used to set these file + descriptors. The function takes ownership of these file + descriptors. IN_FD will be used to read from engine and OUT_FD to + send data to the engine. */ +void +runner_set_fds (runner_t runner, int in_fd, int out_fd) +{ + if (check_already_spawned (runner, "runner_set_fds")) + return; + + if (runner->in_fd != -1) + close (runner->in_fd); + if (runner->out_fd != -1) + close (runner->out_fd); + runner->in_fd = in_fd; + runner->out_fd = out_fd; +} + + +/* Set the PID of the backend engine. After this call the engine is + owned by the runner object. */ +void +runner_set_pid (runner_t runner, pid_t pid) +{ + if (check_already_spawned (runner, "runner_set_fds")) + return; + + runner->pid = pid; +} + + +/* Register the engine handler fucntions HANDLER and HANDLER_CLEANUP + and its private HANDLER_DATA with RUNNER. */ +void +runner_set_handler (runner_t runner, + engine_handler_fnc_t handler, + engine_handler_cleanup_fnc_t handler_cleanup, + void *handler_data) +{ + if (check_already_spawned (runner, "runner_set_handler")) + return; + + runner->handler = handler; + runner->handler_cleanup = handler_cleanup; + runner->handler_data = handler_data; +} + + +/* The thread spawned by runner_spawn. */ +static void * +runner_thread (void *arg) +{ + runner_t runner = arg; + gpg_error_t err; + + log_debug ("starting runner thread\n"); + /* If a status_fp is available, the thread's main task is to read + from that stream and invoke the backend's handler function. This + is done on a line by line base and the line length is limited to + a reasonable value (about 1000 characters). Other work will + continue either due to an EOF of the stream or by demand of the + engine. */ + if (runner->status_fp) + { + int c, cont_line; + unsigned int pos; + char buffer[1024]; + estream_t fp = runner->status_fp; + + pos = 0; + err = 0; + cont_line = 0; + while (!err && !runner->cancel_flag && (c=es_getc (fp)) != EOF) + { + buffer[pos++] = c; + if (pos >= sizeof buffer - 5 || c == '\n') + { + buffer[pos - (c == '\n')] = 0; + if (opt.verbose) + log_info ("%s%s: %s\n", + runner->name, cont_line? "(cont)":"", buffer); + /* We handle only complete lines and ignore any stuff we + possibly had to truncate. That is - at least for the + encfs engine - not an issue because our changes to + the tool make sure that only relatively short prompt + lines are of interest. */ + if (!cont_line && runner->handler) + err = runner->handler (runner->handler_data, + runner, buffer); + pos = 0; + cont_line = (c != '\n'); + } + } + if (!err && runner->cancel_flag) + log_debug ("runner thread noticed cancel flag\n"); + else + log_debug ("runner thread saw EOF\n"); + if (pos) + { + buffer[pos] = 0; + if (opt.verbose) + log_info ("%s%s: %s\n", + runner->name, cont_line? "(cont)":"", buffer); + if (!cont_line && !err && runner->handler) + err = runner->handler (runner->handler_data, + runner, buffer); + } + if (!err && es_ferror (fp)) + { + err = gpg_error_from_syserror (); + log_error ("error reading from %s: %s\n", + runner->name, gpg_strerror (err)); + } + + runner->status_fp = NULL; + es_fclose (fp); + log_debug ("runner thread closed status fp\n"); + } + + /* Now wait for the process to finish. */ + if (!err && runner->pid != (pid_t)(-1)) + { + int exitcode; + + log_debug ("runner thread waiting ...\n"); + err = gnupg_wait_process (runner->name, runner->pid, &exitcode); + runner->pid = (pid_t)(-1); + if (err) + log_error ("running `%s' failed (exitcode=%d): %s\n", + runner->name, exitcode, gpg_strerror (err)); + log_debug ("runner thread waiting finished\n"); + } + + /* Get rid of the runner object (note: it is refcounted). */ + log_debug ("runner thread releasing runner ...\n"); + runner_release (runner); + log_debug ("runner thread runner released\n"); + thread_count--; + + return NULL; +} + + +/* Spawn a new thread to let RUNNER work as a coprocess. */ +gpg_error_t +runner_spawn (runner_t runner) +{ + gpg_error_t err; + pth_attr_t tattr; + pth_t tid; + + if (check_already_spawned (runner, "runner_spawn")) + return gpg_error (GPG_ERR_BUG); + + /* In case we have an input fd, open it as an estream so that the + Pth scheduling will work. The stdio functions don't work with + Pth because they don't call the pth counterparts of read and + write unless linker tricks are used. */ + if (runner->in_fd != -1) + { + estream_t fp; + + fp = es_fdopen (runner->in_fd, "r"); + if (!fp) + { + err = gpg_error_from_syserror (); + log_error ("can't fdopen pipe for reading: %s\n", gpg_strerror (err)); + return err; + } + runner->status_fp = fp; + runner->in_fd = -1; /* Now owned by status_fp. */ + } + + tattr = pth_attr_new (); + pth_attr_set (tattr, PTH_ATTR_JOINABLE, 1); + pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 256*1024); + pth_attr_set (tattr, PTH_ATTR_NAME, runner->name); + + tid = pth_spawn (tattr, runner_thread, runner); + if (!tid) + { + err = gpg_error_from_syserror (); + log_error ("error spawning runner thread: %s\n", gpg_strerror (err)); + return err; + } + /* The scheduler has not yet kicked in, thus we can safely set the + spawned flag and the tid. */ + thread_count++; + runner->spawned = 1; + runner->threadid = tid; + pth_attr_destroy (tattr); + + /* The runner thread is now runnable. */ + + + + return 0; +} + + +/* Cancel a running thread. */ +void +runner_cancel (runner_t runner) +{ + if (runner->spawned) + { + /* FIXME: This does only work if the thread emits status lines. We + need to change the trhead to wait on an event. */ + runner->cancel_flag = 1; + /* For now we use the brutal way and kill the process. */ + gnupg_kill_process (runner->pid); + } +} + + +/* Send a line of data down to the engine. This line may not contain + a binary Nul or a LF character. This function is used by the + engine's handler. */ +gpg_error_t +runner_send_line (runner_t runner, const void *data, size_t datalen) +{ + gpg_error_t err = 0; + + if (!runner->spawned) + { + log_error ("BUG: runner for %s not spawned\n", runner->name); + err = gpg_error (GPG_ERR_INTERNAL); + } + else if (runner->out_fd == -1) + { + log_error ("no output file descriptor for runner %s\n", runner->name); + err = gpg_error (GPG_ERR_EBADF); + } + else if (data && datalen) + { + if (memchr (data, '\n', datalen)) + { + log_error ("LF detected in response data\n"); + err = gpg_error (GPG_ERR_BUG); + } + else if (memchr (data, 0, datalen)) + { + log_error ("Nul detected in response data\n"); + err = gpg_error (GPG_ERR_BUG); + } + else if (writen (runner->out_fd, data, datalen)) + err = gpg_error_from_syserror (); + } + + if (!err) + if (writen (runner->out_fd, "\n", 1)) + err = gpg_error_from_syserror (); + + return err; +} diff --git a/g13/runner.h b/g13/runner.h new file mode 100644 index 000000000..0152f22e4 --- /dev/null +++ b/g13/runner.h @@ -0,0 +1,68 @@ +/* runner.h - Run and watch the backend engines + * Copyright (C) 2009 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG 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 3 of the License, or + * (at your option) any later version. + * + * GnuPG 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef G13_RUNNER_H +#define G13_RUNNER_H + +/* The runner object. */ +struct runner_s; +typedef struct runner_s *runner_t; + +/* Prototypes for the handler functions provided by the engine. */ +typedef gpg_error_t (*engine_handler_fnc_t) (void *opaque, + runner_t runner, + const char *statusline); +typedef void (*engine_handler_cleanup_fnc_t) (void *opaque); + + +/* Return the number of active threads. */ +unsigned int runner_get_threads (void); + +/* Create a new runner object. */ +gpg_error_t runner_new (runner_t *r_runner, const char *name); + +/* Free a runner object. */ +void runner_release (runner_t runner); + +/* Functions to set properties of the runner. */ +void runner_set_fds (runner_t runner, int in_fd, int out_fd); + +void runner_set_pid (runner_t runner, pid_t pid); + +/* Register the handler functions with a runner. */ +void runner_set_handler (runner_t runner, + engine_handler_fnc_t handler, + engine_handler_cleanup_fnc_t handler_cleanup, + void *handler_data); + +/* Start the runner. */ +gpg_error_t runner_spawn (runner_t runner); + +/* Cancel a runner. */ +void runner_cancel (runner_t runner); + +/* Send data back to the engine. This function is used by the + engine's handler. */ +gpg_error_t runner_send_line (runner_t runner, + const void *data, size_t datalen); + + + +#endif /*G13_RUNNER_H*/ + diff --git a/g13/utils.c b/g13/utils.c index 15b4426ef..ef0c572a6 100644 --- a/g13/utils.c +++ b/g13/utils.c @@ -28,6 +28,17 @@ #include "utils.h" +/* Definition of the tuple descriptor object. */ +struct tupledesc_s +{ + unsigned char *data; /* The tuple data. */ + size_t datalen; /* The length of the data. */ + size_t pos; /* The current position as used by next_tuple. */ + int refcount; /* Number of references hold. */ +}; + + + /* Append the TAG and the VALUE to the MEMBUF. There is no error checking here; this is instead done while getting the value back from the membuf. */ @@ -49,3 +60,121 @@ append_tuple (membuf_t *membuf, int tag, const void *value, size_t length) put_membuf (membuf, value, length); } + +/* Create a tuple object by moving the ownership of (DATA,DATALEN) to + a new object. Returns 0 on success and stores the new object at + R_TUPLEHD. The return object must be released using + destroy_tuples(). */ +gpg_error_t +create_tupledesc (tupledesc_t *r_desc, void *data, size_t datalen) +{ + if (datalen < 5 || memcmp (data, "\x00\x00\x00\x01\x01", 5)) + return gpg_error (GPG_ERR_NOT_SUPPORTED); + + *r_desc = xtrymalloc (sizeof **r_desc); + if (!*r_desc) + return gpg_error_from_syserror (); + (*r_desc)->data = data; + (*r_desc)->datalen = datalen; + (*r_desc)->pos = 0; + (*r_desc)->refcount++; + return 0; +} + +/* Unref a tuple descriptor and if the refcount is down to 0 release + its allocated storage. */ +void +destroy_tupledesc (tupledesc_t tupledesc) +{ + if (!tupledesc) + return; + + if (!--tupledesc->refcount) + { + xfree (tupledesc->data); + xfree (tupledesc); + } +} + + +tupledesc_t +ref_tupledesc (tupledesc_t tupledesc) +{ + if (tupledesc) + tupledesc->refcount++; + return tupledesc; +} + + +/* Find the first tuple with tag TAG. On success return a pointer to + its value and store the length of the value at R_LENGTH. If no + tuple was return NULL. For future use by next_tupe, the last + position is stored in the descriptor. */ +const void * +find_tuple (tupledesc_t tupledesc, unsigned int tag, size_t *r_length) +{ + const unsigned char *s; + const unsigned char *s_end; /* Points right behind the data. */ + unsigned int t; + size_t n; + + s = tupledesc->data; + if (!s) + return NULL; + s_end = s + tupledesc->datalen; + while (s < s_end) + { + if (s+3 >= s_end || s + 3 < s) + break; + t = s[0] << 8; + t |= s[1]; + n = s[2] << 8; + n |= s[3]; + s += 4; + if (s + n > s_end || s + n < s) + break; + if (t == tag) + { + tupledesc->pos = (s + n) - tupledesc->data; + *r_length = n; + return s; + } + s += n; + } + return NULL; +} + + +const void * +next_tuple (tupledesc_t tupledesc, unsigned int *r_tag, size_t *r_length) +{ + const unsigned char *s; + const unsigned char *s_end; /* Points right behind the data. */ + unsigned int t; + size_t n; + + s = tupledesc->data; + if (!s) + return NULL; + s_end = s + tupledesc->datalen; + s += tupledesc->pos; + if (s < s_end + && !(s+3 >= s_end || s + 3 < s)) + { + t = s[0] << 8; + t |= s[1]; + n = s[2] << 8; + n |= s[3]; + s += 4; + if (!(s + n > s_end || s + n < s)) + { + tupledesc->pos = (s + n) - tupledesc->data; + *r_tag = t; + *r_length = n; + return s; + } + } + + return NULL; +} + diff --git a/g13/utils.h b/g13/utils.h index c1104f759..ef718d60d 100644 --- a/g13/utils.h +++ b/g13/utils.h @@ -22,10 +22,23 @@ #include "../common/membuf.h" - +/* Append a new tuple to a memory buffer. */ void append_tuple (membuf_t *membuf, int tag, const void *value, size_t length); +/* The tuple descriptor object. */ +struct tupledesc_s; +typedef struct tupledesc_s *tupledesc_t; + +gpg_error_t create_tupledesc (tupledesc_t *r_tupledesc, + void *data, size_t datalen); +void destroy_tupledesc (tupledesc_t tupledesc); +tupledesc_t ref_tupledesc (tupledesc_t tupledesc); +const void *find_tuple (tupledesc_t tupledesc, + unsigned int tag, size_t *r_length); +const void *next_tuple (tupledesc_t tupledesc, + unsigned int *r_tag, size_t *r_length); + #endif /*G13_UTILS_H*/ |