Let scdaemon call a script on status changes

This commit is contained in:
Werner Koch 2006-09-07 15:13:33 +00:00
parent 44393f2ce7
commit 6374763c98
12 changed files with 355 additions and 49 deletions

2
NEWS
View File

@ -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)
-------------------------------------------------

5
TODO
View File

@ -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).

View 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

View 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.

View 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*/
}

View 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*/

View 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.

View File

@ -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

102
doc/examples/scd-event Executable file
View File

@ -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

View 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

View 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):

View 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
}
}
}
}