Let scdaemon call a script on status changes
authorWerner Koch <wk@gnupg.org>
Thu, 7 Sep 2006 15:13:33 +0000 (15:13 +0000)
committerWerner Koch <wk@gnupg.org>
Thu, 7 Sep 2006 15:13:33 +0000 (15:13 +0000)
12 files changed:
NEWS
TODO
agent/call-scd.c
common/ChangeLog
common/exechelp.c
common/exechelp.h
doc/ChangeLog
doc/Makefile.am
doc/examples/scd-event [new file with mode: 0755]
doc/scdaemon.texi
scd/ChangeLog
scd/command.c

diff --git a/NEWS b/NEWS
index e60c6f7..ff5feb9 100644 (file)
--- 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 1b0a7b4..ddc8892 100644 (file)
--- 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).
index 737dbad..3dc8691 100644 (file)
@@ -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
index 7a917fd..88dfdff 100644 (file)
@@ -1,3 +1,10 @@
+2006-09-07  Werner Koch  <wk@g10code.com>
+
+       * 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  <wk@g10code.com>
 
        * maperror.c: Removed.
index e64b690..cfb76c2 100644 (file)
@@ -28,6 +28,7 @@
 #include <assert.h>
 #include <signal.h>
 #include <unistd.h> 
+#include <fcntl.h>
 #ifdef USE_GNU_PTH      
 #include <pth.h>
 #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*/
+}
index 1df029b..f78a43c 100644 (file)
@@ -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*/
index 4a86e45..87238a8 100644 (file)
@@ -1,3 +1,10 @@
+2006-09-07  Werner Koch  <wk@g10code.com>
+
+       * scdaemon.texi (Scdaemon Configuration): New.
+
+       * examples/scd-event: Event handler for sdaemon.
+       * examples/: New directory
+
 2006-08-22  Werner Koch  <wk@g10code.com>
 
        * yat2m.c (parse_file): Added code to skip a line after @mansect.
index 77e48aa..dcef09c 100644 (file)
 
 ## 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 (executable)
index 0000000..1d03187
--- /dev/null
@@ -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 <<EOF
+Usage: $PGM [options]
+$PGM is called by scdaemon on card reader status changes
+
+Options:
+  --reader-port N        Reports change for port N
+  --old-code 0xNNNN      Previous status code
+  --old-code 0xNNNN      Current status code
+  --status USABLE|ACTIVE|PRESENT}NOCARD 
+                         Human readable status code
+
+Environment:
+
+GNUPGHOME=DIR            Set to the active hmedir
+
+EOF
+          exit 0
+          ;;
+    
+      --reader-port)  
+          prev=reader_port
+          ;;
+      --reader-port=*)
+          reader_port="$optarg"
+          ;;
+      --old-code)  
+          prev=old_code
+          ;;
+      --old-code=*)
+          old_code="$optarg"
+          ;;
+      --new-code)  
+          prev=new_code
+          ;;
+      --new-code=*)
+          new_code="$optarg"
+          ;;
+      --status)  
+          prev=status
+          ;;
+      --new-code=*)
+          status="$optarg"
+          ;;
+
+      -*)
+          echo "$PGM: invalid option $tick$arg'" >&2
+          exit 1
+          ;;
+
+      *)
+          break
+          ;;
+  esac
+  shift
+done
+if [ -n "$prev" ]; then
+  echo "$PGM: argument missing for option $tick$prev'" >&2
+  exit 1
+fi
+
+cat <<EOF
+========================
+port:     $reader_port
+old-code: $old_code
+new-code: $new_code
+status:   $status
+EOF
+
+if [ x$status = xUSABLE ]; then
+    gpg --batch --card-status 2>&1
+fi
+
index f507520..91b056a 100644 (file)
@@ -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
+
index ee0e2ca..a62b8b0 100644 (file)
@@ -1,3 +1,8 @@
+2006-09-07  Werner Koch  <wk@g10code.com>
+
+       * command.c (update_reader_status_file): Execute an event handler
+       if available.
+
 2006-09-06  Werner Koch  <wk@g10code.com>
 
        * apdu.c (pcsc_end_transaction): 
index 2d32c08..573a917 100644 (file)
@@ -37,6 +37,7 @@
 #include <ksba.h>
 #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
               }
+
         }
     }
 }