From 6374763c985a36c25d3cea9a8a83b51f42708160 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 7 Sep 2006 15:13:33 +0000 Subject: [PATCH] Let scdaemon call a script on status changes --- NEWS | 2 + TODO | 5 -- agent/call-scd.c | 2 +- common/ChangeLog | 7 ++ common/exechelp.c | 168 +++++++++++++++++++++++++++++++---------- common/exechelp.h | 12 +++ doc/ChangeLog | 7 ++ doc/Makefile.am | 5 +- doc/examples/scd-event | 102 +++++++++++++++++++++++++ doc/scdaemon.texi | 46 ++++++++++- scd/ChangeLog | 5 ++ scd/command.c | 43 +++++++++++ 12 files changed, 355 insertions(+), 49 deletions(-) create mode 100755 doc/examples/scd-event diff --git a/NEWS b/NEWS index e60c6f7a3..ff5feb9ae 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,8 @@ Noteworthy changes in version 1.9.23 * API change in gpg-agent's pkdecrypt command. Thus an older gpgsm may not be used with the current gpg-agent. + * The scdaemon will now call a script on reader status changes. + Noteworthy changes in version 1.9.22 (2006-07-27) ------------------------------------------------- diff --git a/TODO b/TODO index 1b0a7b4ef..ddc889264 100644 --- a/TODO +++ b/TODO @@ -80,9 +80,6 @@ might want to have an agent context for each service request * doc/ ** Explain how to setup a root CA key as trusted ** Explain how trustlist.txt might be managed. -** Write a script to generate man pages from texi. - In progress (yatm) - * Windows port ** gpgsm's LISTKEYS does not yet work @@ -91,8 +88,6 @@ might want to have an agent context for each service request This means we can't reread a configuration ** No card status notifications. - - * sm/ ** check that we issue NO_SECKEY xxx if a -u key was not found We don't. The messages retruned are also wrong (recipient vs. signer). diff --git a/agent/call-scd.c b/agent/call-scd.c index 737dbad61..3dc869137 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -193,7 +193,7 @@ atfork_cb (void *opaque, int where) /* Fork off the SCdaemon if this has not already been done. Lock the daemon and make sure that a proper context has been setup in CTRL. - Thsi fucntion might also lock the daemon, which means that the + This function might also lock the daemon, which means that the caller must call unlock_scd after this fucntion has returned success and the actual Assuan transaction been done. */ static int diff --git a/common/ChangeLog b/common/ChangeLog index 7a917fdd7..88dfdff9c 100644 --- a/common/ChangeLog +++ b/common/ChangeLog @@ -1,3 +1,10 @@ +2006-09-07 Werner Koch + + * exechelp.c (gnupg_spawn_process): Factor out post fork code to .. + (do_exec): .. new function. Allow passing of -1 for the fds. + (gnupg_spawn_process): Terminate gcrypt's secure memory in the child. + (gnupg_spawn_process_detached): New. + 2006-09-06 Werner Koch * maperror.c: Removed. diff --git a/common/exechelp.c b/common/exechelp.c index e64b69022..cfb76c2f5 100644 --- a/common/exechelp.c +++ b/common/exechelp.c @@ -28,6 +28,7 @@ #include #include #include +#include #ifdef USE_GNU_PTH #include #endif @@ -159,6 +160,67 @@ create_inheritable_pipe (int filedes[2]) #endif /*HAVE_W32_SYSTEM*/ +#ifndef HAVE_W32_SYSTEM +/* The exec core used right after the fork. This will never return. */ +static void +do_exec (const char *pgmname, const char *argv[], + int fd_in, int fd_out, int fd_err, + void (*preexec)(void) ) +{ + char **arg_list; + int n, i, j; + int fds[3]; + + fds[0] = fd_in; + fds[1] = fd_out; + fds[2] = fd_err; + + /* Create the command line argument array. */ + i = 0; + if (argv) + while (argv[i]) + i++; + arg_list = xcalloc (i+2, sizeof *arg_list); + arg_list[0] = strrchr (pgmname, '/'); + if (arg_list[0]) + arg_list[0]++; + else + arg_list[0] = xstrdup (pgmname); + if (argv) + for (i=0,j=1; argv[i]; i++, j++) + arg_list[j] = (char*)argv[i]; + + /* Connect the standard files. */ + for (i=0; i <= 2; i++) + { + if (fds[i] == -1 ) + { + fds[i] = open ("/dev/null", i? O_WRONLY : O_RDONLY); + if (fds[i] == -1) + log_fatal ("failed to open `%s': %s\n", + "/dev/null", strerror (errno)); + } + else if (fds[i] != i && dup2 (fds[i], i) == -1) + log_fatal ("dup2 std%s failed: %s\n", + i==0?"in":i==1?"out":"err", strerror (errno)); + } + + /* Close all other files. */ + n = sysconf (_SC_OPEN_MAX); + if (n < 0) + n = MAX_OPEN_FDS; + for (i=3; i < n; i++) + close(i); + errno = 0; + + if (preexec) + preexec (); + execv (pgmname, arg_list); + /* No way to print anything, as we have closed all streams. */ + _exit (127); +} +#endif /*!HAVE_W32_SYSTEM*/ + /* Fork and exec the PGMNAME, connect the file descriptor of INFILE to stdin, write the output to OUTFILE, return a new stream in @@ -325,47 +387,10 @@ gnupg_spawn_process (const char *pgmname, const char *argv[], if (!*pid) { - /* Child. */ - char **arg_list; - int n, i, j; - - /* Create the command line argument array. */ - for (i=0; argv[i]; i++) - ; - arg_list = xcalloc (i+2, sizeof *arg_list); - arg_list[0] = strrchr (pgmname, '/'); - if (arg_list[0]) - arg_list[0]++; - else - arg_list[0] = xstrdup (pgmname); - for (i=0,j=1; argv[i]; i++, j++) - arg_list[j] = (char*)argv[i]; - - /* Connect the infile to stdin. */ - if (fd != 0 && dup2 (fd, 0) == -1) - log_fatal ("dup2 stdin failed: %s\n", strerror (errno)); - - /* Connect the outfile to stdout. */ - if (fdout != 1 && dup2 (fdout, 1) == -1) - log_fatal ("dup2 stdout failed: %s\n", strerror (errno)); - - /* Connect stderr to our pipe. */ - if (rp[1] != 2 && dup2 (rp[1], 2) == -1) - log_fatal ("dup2 stderr failed: %s\n", strerror (errno)); - - /* Close all other files. */ - n = sysconf (_SC_OPEN_MAX); - if (n < 0) - n = MAX_OPEN_FDS; - for (i=3; i < n; i++) - close(i); - errno = 0; - - if (preexec) - preexec (); - execv (pgmname, arg_list); - /* No way to print anything, as we have closed all streams. */ - _exit (127); + gcry_control (GCRYCTL_TERM_SECMEM); + /* Run child. */ + do_exec (pgmname, argv, fd, fdout, rp[1], preexec); + /*NOTREACHED*/ } /* Parent. */ @@ -481,3 +506,64 @@ gnupg_wait_process (const char *pgmname, pid_t pid) } + +/* Spawn a new process and immediatley detach from it. The name of + the program to exec is PGMNAME and its arguments are in ARGV (the + programname is automatically passed as first argument). + Environment strings in ENVP are set. An error is returned if + pgmname is not executable; to make this work it is necessary to + provide an absolute file name. All standard file descriptors are + connected to /dev/null. */ +gpg_error_t +gnupg_spawn_process_detached (const char *pgmname, const char *argv[], + const char *envp[] ) +{ +#ifdef HAVE_W32_SYSTEM + return gpg_error (GPG_ERR_NOT_IMPLEMENTED); +#else + pid_t pid; + int i; + + if (getuid() != geteuid()) + return gpg_error (GPG_ERR_BUG); + + if (access (pgmname, X_OK)) + return gpg_error_from_errno (errno); + +#ifdef USE_GNU_PTH + pid = pth_fork? pth_fork () : fork (); +#else + pid = fork (); +#endif + if (pid == (pid_t)(-1)) + { + log_error (_("error forking process: %s\n"), strerror (errno)); + return gpg_error_from_errno (errno); + } + if (!pid) + { + gcry_control (GCRYCTL_TERM_SECMEM); + if (setsid() == -1 || chdir ("/")) + _exit (1); + pid = fork (); /* Double fork to let init takes over the new child. */ + if (pid == (pid_t)(-1)) + _exit (1); + if (pid) + _exit (0); /* Let the parent exit immediately. */ + + if (envp) + for (i=0; envp[i]; i++) + putenv (xstrdup (envp[i])); + + do_exec (pgmname, argv, -1, -1, -1, NULL); + + /*NOTREACHED*/ + } + + if (waitpid (pid, NULL, 0) == -1) + log_error ("waitpid failed in gnupg_spawn_process_detached: %s", + strerror (errno)); + + return 0; +#endif /* !HAVE_W32_SYSTEM*/ +} diff --git a/common/exechelp.h b/common/exechelp.h index 1df029b7e..f78a43c6b 100644 --- a/common/exechelp.h +++ b/common/exechelp.h @@ -43,4 +43,16 @@ gpg_error_t gnupg_spawn_process (const char *pgmname, const char *argv[], gpg_error_t gnupg_wait_process (const char *pgmname, pid_t pid); +/* Spawn a new process and immediatley detach from it. The name of + the program to exec is PGMNAME and its arguments are in ARGV (the + programname is automatically passed as first argument). + Environment strings in ENVP are set. An error is returned if + pgmname is not executable; to make this work it is necessary to + provide an absolute file name. */ +gpg_error_t gnupg_spawn_process_detached (const char *pgmname, + const char *argv[], + const char *envp[] ); + + + #endif /*GNUPG_COMMON_EXECHELP_H*/ diff --git a/doc/ChangeLog b/doc/ChangeLog index 4a86e4509..87238a8e6 100644 --- a/doc/ChangeLog +++ b/doc/ChangeLog @@ -1,3 +1,10 @@ +2006-09-07 Werner Koch + + * scdaemon.texi (Scdaemon Configuration): New. + + * examples/scd-event: Event handler for sdaemon. + * examples/: New directory + 2006-08-22 Werner Koch * yat2m.c (parse_file): Added code to skip a line after @mansect. diff --git a/doc/Makefile.am b/doc/Makefile.am index 77e48aaed..dcef09c18 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -19,13 +19,16 @@ ## Process this file with automake to produce Makefile.in +examples=examples/scd-event + EXTRA_DIST = DETAILS HACKING TRANSLATE OpenPGP KEYSERVER samplekeys.asc \ gnupg-badge-openpgp.eps gnupg-badge-openpgp.jpg \ gnupg-badge-openpgp.pdf \ gnupg-card-architecture.eps gnupg-card-architecture.png \ gnupg-card-architecture.pdf \ faq.raw FAQ faq.html gnupg7.texi \ - opt-homedir.texi see-also-note.texi + opt-homedir.texi see-also-note.texi \ + $(examples) BUILT_SOURCES = gnupg-card-architecture.eps gnupg-card-architecture.png \ gnupg-card-architecture.pdf FAQ faq.html diff --git a/doc/examples/scd-event b/doc/examples/scd-event new file mode 100755 index 000000000..1d031871d --- /dev/null +++ b/doc/examples/scd-event @@ -0,0 +1,102 @@ +#!/bin/sh +# Sample script for scdaemon event mechanism. + +#exec >>/tmp/scd-event.log + +PGM=scd-event + +reader_port= +old_code=0x0000 +new_code=0x0000 +status= + +tick='`' +prev= +while [ $# -gt 0 ]; do + arg="$1" + case $arg in + -*=*) optarg=$(echo "X$arg" | sed -e '1s/^X//' -e 's/[-_a-zA-Z0-9]*=//') + ;; + *) optarg= + ;; + esac + if [ -n "$prev" ]; then + eval "$prev=\$arg" + prev= + shift + continue + fi + case $arg in + --help|-h) + cat <&2 + exit 1 + ;; + + *) + break + ;; + esac + shift +done +if [ -n "$prev" ]; then + echo "$PGM: argument missing for option $tick$prev'" >&2 + exit 1 +fi + +cat <&1 +fi + diff --git a/doc/scdaemon.texi b/doc/scdaemon.texi index f50752076..91b056aeb 100644 --- a/doc/scdaemon.texi +++ b/doc/scdaemon.texi @@ -48,6 +48,7 @@ options. * Scdaemon Commands:: List of all commands. * Scdaemon Options:: List of all options. * Card applications:: Description of card applications. +* Scdaemon Configuration:: Configuration files. * Scdaemon Examples:: Some usage examples. * Scdaemon Protocol:: The protocol the daemon uses. @end menu @@ -320,6 +321,41 @@ This is common fraqmework for smart card applications. It is used by @command{gpgsm}. +@c ******************************************* +@c *************** **************** +@c *************** FILES **************** +@c *************** **************** +@c ******************************************* +@mansect files +@node Scdaemon Configuration +@section Configuration files + +There are a few configuration files to control certain aspects of +@command{scdaemons}'s operation. Unless noted, they are expected in the +current home directory (@pxref{option --homedir}). + +@table @file + +@item scdaemon.conf +@cindex scdaemon.conf +This is the standard configuration file read by @command{scdaemon} on +startup. It may contain any valid long option; the leading two dashes +may not be entered and the option may not be abbreviated. This default +name may be changed on the command line (@pxref{option --options}). + +@item scd-event +@cindex scd-event +If this file is present and executable, it will be called on veyer card +reader's status changed. An example of this script is provided with the +distribution + +@item reader_@var{n}.status +This file is created by @command{sdaemon} to let other applications now +about reader status changes. Its use is now deprecated in favor of +@file{scd-event}. + +@end table + @c @c Examples @@ -339,7 +375,7 @@ $ scdaemon --server -v @c @c Assuan Protocol @c -@mansect assuan +@manpause @node Scdaemon Protocol @section Scdaemon's Assuan Protocol @@ -621,3 +657,11 @@ Using the option @code{--more} handles the card status word MORE_DATA +@mansect see also +@ifset isman +@command{gpg-agent}(1), +@command{gpgsm}(1), +@command{gpg2}(1) +@end ifset +@include see-also-note.texi + diff --git a/scd/ChangeLog b/scd/ChangeLog index ee0e2ca93..a62b8b06a 100644 --- a/scd/ChangeLog +++ b/scd/ChangeLog @@ -1,3 +1,8 @@ +2006-09-07 Werner Koch + + * command.c (update_reader_status_file): Execute an event handler + if available. + 2006-09-06 Werner Koch * apdu.c (pcsc_end_transaction): diff --git a/scd/command.c b/scd/command.c index 2d32c0853..573a91706 100644 --- a/scd/command.c +++ b/scd/command.c @@ -37,6 +37,7 @@ #include #include "app-common.h" #include "apdu.h" /* Required for apdu_*_reader (). */ +#include "exechelp.h" /* Maximum length allowed as a PIN; used for INQUIRE NEEDPIN */ #define MAXLEN_PIN 100 @@ -1778,6 +1779,47 @@ update_reader_status_file (void) } xfree (fname); + /* If a status script is executable, run it. */ + { + const char *args[9], *envs[2]; + char numbuf1[30], numbuf2[3], numbuf3[30]; + char *homestr, *envstr; + gpg_error_t err; + + homestr = make_filename (opt.homedir, NULL); + if (asprintf (&envstr, "GNUPGHOME=%s", homestr) < 0) + log_error ("out of core while building environment\n"); + else + { + envs[0] = envstr; + envs[1] = NULL; + + sprintf (numbuf1, "%d", ss->slot); + sprintf (numbuf2, "0x%04X", ss->status); + sprintf (numbuf3, "0x%04X", status); + args[0] = "--reader-port"; + args[1] = numbuf1; + args[2] = "--old-code"; + args[3] = numbuf2; + args[4] = "--new-code"; + args[5] = numbuf3; + args[6] = "--status"; + args[7] = ((status & 1)? "USABLE": + (status & 4)? "ACTIVE": + (status & 2)? "PRESENT": "NOCARD"); + args[8] = NULL; + + fname = make_filename (opt.homedir, "scd-event", NULL); + err = gnupg_spawn_process_detached (fname, args, envs); + if (err && gpg_err_code (err) != GPG_ERR_ENOENT) + log_error ("failed to run event handler `%s': %s\n", + fname, gpg_strerror (err)); + xfree (fname); + free (envstr); + } + xfree (homestr); + } + /* Set the card removed flag for all current sessions. We will set this on any card change because a reset or SERIALNO request must be done in any case. */ @@ -1802,6 +1844,7 @@ update_reader_status_file (void) kill (pid, signo); #endif } + } } }