diff options
author | Werner Koch <wk@gnupg.org> | 2010-07-23 18:16:14 +0200 |
---|---|---|
committer | Werner Koch <wk@gnupg.org> | 2010-07-23 18:16:14 +0200 |
commit | a22c38baad4113ce477b2e312e0bec365c0bd6b3 (patch) | |
tree | bfa3cedf6beb152420e22687eed1f29bad758ab9 /dirmngr | |
parent | . (diff) | |
download | gnupg2-a22c38baad4113ce477b2e312e0bec365c0bd6b3.tar.xz gnupg2-a22c38baad4113ce477b2e312e0bec365c0bd6b3.zip |
Some work on the dirmngr
Diffstat (limited to 'dirmngr')
-rw-r--r-- | dirmngr/ChangeLog | 20 | ||||
-rw-r--r-- | dirmngr/Makefile.am | 2 | ||||
-rw-r--r-- | dirmngr/crlcache.c | 2 | ||||
-rw-r--r-- | dirmngr/crlfetch.h | 5 | ||||
-rw-r--r-- | dirmngr/dirmngr.c | 37 | ||||
-rw-r--r-- | dirmngr/ldap-wrapper-ce.c | 333 | ||||
-rw-r--r-- | dirmngr/ldap-wrapper.c | 747 | ||||
-rw-r--r-- | dirmngr/ldap-wrapper.h | 33 | ||||
-rw-r--r-- | dirmngr/ldap.c | 658 | ||||
-rw-r--r-- | dirmngr/ldapserver.h | 4 |
10 files changed, 1144 insertions, 697 deletions
diff --git a/dirmngr/ChangeLog b/dirmngr/ChangeLog index c95a2f2d6..8af2f3047 100644 --- a/dirmngr/ChangeLog +++ b/dirmngr/ChangeLog @@ -1,3 +1,23 @@ +2010-07-19 Werner Koch <wk@g10code.com> + + * dirmngr.c: Include ldap-wrapper.h. + (launch_reaper_thread): Move code to ... + * ldap-wrapper.c (ldap_wrapper_launch_thread): .. here. Change + callers. + (ldap_wrapper_thread): Rename to ... + (wrapper_thread): this and make local. + + * ldap.c (destroy_wrapper, print_log_line) + (read_log_data, ldap_wrapper_thread) + (ldap_wrapper_wait_connections, ldap_wrapper_release_context) + (ldap_wrapper_connection_cleanup, reader_callback, ldap_wrapper): + Factor code out to ... + * ldap-wrapper.c: new. + (ldap_wrapper): Make public. + (read_buffer): Copy from ldap.c. + * ldap-wrapper.h: New. + * Makefile.am (dirmngr_SOURCES): Add new files. + 2010-07-16 Werner Koch <wk@g10code.com> * http.c, http.h: Remove. diff --git a/dirmngr/Makefile.am b/dirmngr/Makefile.am index 537bdf035..9d58e9cd3 100644 --- a/dirmngr/Makefile.am +++ b/dirmngr/Makefile.am @@ -39,7 +39,7 @@ noinst_HEADERS = dirmngr.h crlcache.h crlfetch.h misc.h dirmngr_SOURCES = dirmngr.c dirmngr.h server.c crlcache.c crlfetch.c \ ldapserver.h ldapserver.c certcache.c certcache.h \ cdb.h cdblib.c ldap.c misc.c dirmngr-err.h \ - ocsp.c ocsp.h validate.c validate.h + ocsp.c ocsp.h validate.c validate.h ldap-wrapper.c ldap-wrapper.h dirmngr_LDADD = $(libcommonpth) ../gl/libgnu.a $(DNSLIBS) $(LIBASSUAN_LIBS) \ $(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(PTH_LIBS) $(LIBINTL) $(LIBICONV) diff --git a/dirmngr/crlcache.c b/dirmngr/crlcache.c index aeb6304b0..441ae9ee0 100644 --- a/dirmngr/crlcache.c +++ b/dirmngr/crlcache.c @@ -891,7 +891,7 @@ update_dir (crl_cache_t cache) xfree (line); } - if (!es_ferror (fp) && !ferror (es_fpout) && !lineerr) + if (!es_ferror (fp) && !es_ferror (fpout) && !lineerr) { /* Write out the remaining entries. */ for (e= cache->entries; e; e = e->next) diff --git a/dirmngr/crlfetch.h b/dirmngr/crlfetch.h index e42196dc7..dd282385d 100644 --- a/dirmngr/crlfetch.h +++ b/dirmngr/crlfetch.h @@ -61,11 +61,6 @@ void crl_close_reader (ksba_reader_t reader); /*-- ldap.c --*/ -void *ldap_wrapper_thread (void*); -void ldap_wrapper_wait_connections (void); -void ldap_wrapper_release_context (ksba_reader_t reader); -void ldap_wrapper_connection_cleanup (ctrl_t); - gpg_error_t url_fetch_ldap (ctrl_t ctrl, const char *url, const char *host, int port, ksba_reader_t *reader); diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c index 5a913905d..52efb9be4 100644 --- a/dirmngr/dirmngr.c +++ b/dirmngr/dirmngr.c @@ -54,6 +54,7 @@ #include "misc.h" #include "ldapserver.h" #include "asshelp.h" +#include "ldap-wrapper.h" /* The plain Windows version uses the windows service system. For example to start the service you may use "sc start dirmngr". @@ -393,32 +394,6 @@ wrong_args (const char *text) } -/* Helper to start the reaper thread for the ldap wrapper. */ -static void -launch_reaper_thread (void) -{ - static int done; - pth_attr_t tattr; - - if (done) - return; - done = 1; - - tattr = pth_attr_new(); - pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0); - pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 256*1024); - pth_attr_set (tattr, PTH_ATTR_NAME, "ldap-reaper"); - - if (!pth_spawn (tattr, ldap_wrapper_thread, NULL)) - { - log_error (_("error spawning ldap wrapper reaper thread: %s\n"), - strerror (errno) ); - dirmngr_exit (1); - } - pth_attr_destroy (tattr); -} - - /* Helper to stop the reaper thread for the ldap wrapper. */ static void shutdown_reaper (void) @@ -938,7 +913,7 @@ main (int argc, char **argv) log_debug ("... okay\n"); } - launch_reaper_thread (); + ldap_wrapper_launch_thread (); cert_cache_init (); crl_cache_init (); start_command_handler (ASSUAN_INVALID_FD); @@ -1101,7 +1076,7 @@ main (int argc, char **argv) } #endif - launch_reaper_thread (); + ldap_wrapper_launch_thread (); cert_cache_init (); crl_cache_init (); #ifdef USE_W32_SERVICE @@ -1127,7 +1102,7 @@ main (int argc, char **argv) /* Just list the CRL cache and exit. */ if (argc) wrong_args ("--list-crls"); - launch_reaper_thread (); + ldap_wrapper_launch_thread (); crl_cache_init (); crl_cache_list (es_stdout); } @@ -1138,7 +1113,7 @@ main (int argc, char **argv) memset (&ctrlbuf, 0, sizeof ctrlbuf); dirmngr_init_default_ctrl (&ctrlbuf); - launch_reaper_thread (); + ldap_wrapper_launch_thread (); cert_cache_init (); crl_cache_init (); if (!argc) @@ -1160,7 +1135,7 @@ main (int argc, char **argv) memset (&ctrlbuf, 0, sizeof ctrlbuf); dirmngr_init_default_ctrl (&ctrlbuf); - launch_reaper_thread (); + ldap_wrapper_launch_thread (); cert_cache_init (); crl_cache_init (); rc = crl_fetch (&ctrlbuf, argv[0], &reader); diff --git a/dirmngr/ldap-wrapper-ce.c b/dirmngr/ldap-wrapper-ce.c new file mode 100644 index 000000000..dd594fdbd --- /dev/null +++ b/dirmngr/ldap-wrapper-ce.c @@ -0,0 +1,333 @@ +/* ldap-wrapper-ce.c - LDAP access via W32 threads + * Copyright (C) 2010 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/>. + */ + +/* + Alternative wrapper for use with WindowsCE. Under WindowsCE the + number of processes is strongly limited (32 processes including the + kernel processes) and thus we don't use the process approach but + implement a wrapper based on native threads. + + See ldap-wrapper.c for the standard wrapper interface. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <time.h> +#include <pth.h> + +#include "dirmngr.h" +#include "misc.h" +#include "ldap-wrapper.h" + + + +/* To keep track of the LDAP wrapper state we use this structure. */ +struct wrapper_context_s +{ + struct wrapper_context_s *next; + + pid_t pid; /* The pid of the wrapper process. */ + int printable_pid; /* Helper to print diagnostics after the process has + been cleaned up. */ + int fd; /* Connected with stdout of the ldap wrapper. */ + gpg_error_t fd_error; /* Set to the gpg_error of the last read error + if any. */ + int log_fd; /* Connected with stderr of the ldap wrapper. */ + pth_event_t log_ev; + ctrl_t ctrl; /* Connection data. */ + int ready; /* Internally used to mark to be removed contexts. */ + ksba_reader_t reader; /* The ksba reader object or NULL. */ + char *line; /* Used to print the log lines (malloced). */ + size_t linesize;/* Allocated size of LINE. */ + size_t linelen; /* Use size of LINE. */ + time_t stamp; /* The last time we noticed ativity. */ +}; + + + +/* We keep a global list of spawed wrapper process. A separate thread + makes use of this list to log error messages and to watch out for + finished processes. */ +static struct wrapper_context_s *wrapper_list; + +/* We need to know whether we are shutting down the process. */ +static int shutting_down; + + + +/* Start the reaper thread for this wrapper. */ +void +ldap_wrapper_launch_thread (void) +{ + static int done; + pth_attr_t tattr; + + if (done) + return; + done = 1; + + tattr = pth_attr_new(); + pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0); + pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 256*1024); + pth_attr_set (tattr, PTH_ATTR_NAME, "ldap-reaper"); + + if (!pth_spawn (tattr, ldap_wrapper_thread, NULL)) + { + log_error (_("error spawning ldap wrapper reaper thread: %s\n"), + strerror (errno) ); + dirmngr_exit (1); + } + pth_attr_destroy (tattr); +} + + + + + +/* Wait until all ldap wrappers have terminated. We assume that the + kill has already been sent to all of them. */ +void +ldap_wrapper_wait_connections () +{ + shutting_down = 1; + while (wrapper_list) + pth_yield (NULL); +} + + +/* This function is to be used to release a context associated with the + given reader object. */ +void +ldap_wrapper_release_context (ksba_reader_t reader) +{ + if (!reader ) + return; + + for (ctx=wrapper_list; ctx; ctx=ctx->next) + if (ctx->reader == reader) + { + if (DBG_LOOKUP) + log_info ("releasing ldap worker c=%p pid=%d/%d rdr=%p ctrl=%p/%d\n", + ctx, + (int)ctx->pid, (int)ctx->printable_pid, + ctx->reader, + ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0); + + ctx->reader = NULL; + SAFE_PTH_CLOSE (ctx->fd); + if (ctx->ctrl) + { + ctx->ctrl->refcount--; + ctx->ctrl = NULL; + } + if (ctx->fd_error) + log_info (_("reading from ldap wrapper %d failed: %s\n"), + ctx->printable_pid, gpg_strerror (ctx->fd_error)); + break; + } +} + +/* Cleanup all resources held by the connection associated with + CTRL. This is used after a cancel to kill running wrappers. */ +void +ldap_wrapper_connection_cleanup (ctrl_t ctrl) +{ + struct wrapper_context_s *ctx; + + for (ctx=wrapper_list; ctx; ctx=ctx->next) + if (ctx->ctrl && ctx->ctrl == ctrl) + { + ctx->ctrl->refcount--; + ctx->ctrl = NULL; + if (ctx->pid != (pid_t)(-1)) + gnupg_kill_process (ctx->pid); + if (ctx->fd_error) + log_info (_("reading from ldap wrapper %d failed: %s\n"), + ctx->printable_pid, gpg_strerror (ctx->fd_error)); + } +} + +/* Fork and exec the LDAP wrapper and returns a new libksba reader + object at READER. ARGV is a NULL terminated list of arguments for + the wrapper. The function returns 0 on success or an error code. + + Special hack to avoid passing a password through the command line + which is globally visible: If the first element of ARGV is "--pass" + it will be removed and instead the environment variable + DIRMNGR_LDAP_PASS will be set to the next value of ARGV. On modern + OSes the environment is not visible to other users. For those old + systems where it can't be avoided, we don't want to go into the + hassle of passing the password via stdin; it's just too complicated + and an LDAP password used for public directory lookups should not + be that confidential. */ +gpg_error_t +ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, const char *argv[]) +{ + gpg_error_t err; + pid_t pid; + struct wrapper_context_s *ctx; + int i; + int j; + const char **arg_list; + const char *pgmname; + int outpipe[2], errpipe[2]; + + /* It would be too simple to connect stderr just to our logging + stream. The problem is that if we are running multi-threaded + everything gets intermixed. Clearly we don't want this. So the + only viable solutions are either to have another thread + responsible for logging the messages or to add an option to the + wrapper module to do the logging on its own. Given that we anyway + need a way to rip the child process and this is best done using a + general ripping thread, that thread can do the logging too. */ + + *reader = NULL; + + /* Files: We need to prepare stdin and stdout. We get stderr from + the function. */ + if (!opt.ldap_wrapper_program || !*opt.ldap_wrapper_program) + pgmname = gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR_LDAP); + else + pgmname = opt.ldap_wrapper_program; + + /* Create command line argument array. */ + for (i = 0; argv[i]; i++) + ; + arg_list = xtrycalloc (i + 2, sizeof *arg_list); + if (!arg_list) + { + err = gpg_error_from_syserror (); + log_error (_("error allocating memory: %s\n"), strerror (errno)); + return err; + } + for (i = j = 0; argv[i]; i++, j++) + if (!i && argv[i + 1] && !strcmp (*argv, "--pass")) + { + arg_list[j] = "--env-pass"; + setenv ("DIRMNGR_LDAP_PASS", argv[1], 1); + i++; + } + else + arg_list[j] = (char*) argv[i]; + + ctx = xtrycalloc (1, sizeof *ctx); + if (!ctx) + { + err = gpg_error_from_syserror (); + log_error (_("error allocating memory: %s\n"), strerror (errno)); + xfree (arg_list); + return err; + } + + err = gnupg_create_inbound_pipe (outpipe); + if (!err) + { + err = gnupg_create_inbound_pipe (errpipe); + if (err) + { + close (outpipe[0]); + close (outpipe[1]); + } + } + if (err) + { + log_error (_("error creating pipe: %s\n"), gpg_strerror (err)); + xfree (arg_list); + xfree (ctx); + return err; + } + + err = gnupg_spawn_process_fd (pgmname, arg_list, + -1, outpipe[1], errpipe[1], &pid); + xfree (arg_list); + close (outpipe[1]); + close (errpipe[1]); + if (err) + { + close (outpipe[0]); + close (errpipe[0]); + xfree (ctx); + return err; + } + + ctx->pid = pid; + ctx->printable_pid = (int) pid; + ctx->fd = outpipe[0]; + ctx->log_fd = errpipe[0]; + ctx->log_ev = pth_event (PTH_EVENT_FD | PTH_UNTIL_FD_READABLE, ctx->log_fd); + if (! ctx->log_ev) + { + xfree (ctx); + return gpg_error_from_syserror (); + } + ctx->ctrl = ctrl; + ctrl->refcount++; + ctx->stamp = time (NULL); + + err = ksba_reader_new (reader); + if (!err) + err = ksba_reader_set_cb (*reader, reader_callback, ctx); + if (err) + { + log_error (_("error initializing reader object: %s\n"), + gpg_strerror (err)); + destroy_wrapper (ctx); + ksba_reader_release (*reader); + *reader = NULL; + return err; + } + + /* Hook the context into our list of running wrappers. */ + ctx->reader = *reader; + ctx->next = wrapper_list; + wrapper_list = ctx; + if (opt.verbose) + log_info ("ldap wrapper %d started (reader %p)\n", + (int)ctx->pid, ctx->reader); + + /* Need to wait for the first byte so we are able to detect an empty + output and not let the consumer see an EOF without further error + indications. The CRL loading logic assumes that after return + from this function, a failed search (e.g. host not found ) is + indicated right away. */ + { + unsigned char c; + + err = read_buffer (*reader, &c, 1); + if (err) + { + ldap_wrapper_release_context (*reader); + ksba_reader_release (*reader); + *reader = NULL; + if (gpg_err_code (err) == GPG_ERR_EOF) + return gpg_error (GPG_ERR_NO_DATA); + else + return err; + } + ksba_reader_unread (*reader, &c, 1); + } + + return 0; +} diff --git a/dirmngr/ldap-wrapper.c b/dirmngr/ldap-wrapper.c new file mode 100644 index 000000000..8d03ca756 --- /dev/null +++ b/dirmngr/ldap-wrapper.c @@ -0,0 +1,747 @@ +/* ldap-wrapper.c - LDAP access via a wrapper process + * Copyright (C) 2004, 2005, 2007, 2008 g10 Code GmbH + * Copyright (C) 2010 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/>. + */ + +/* + We can't use LDAP directly for these reasons: + + 1. On some systems the LDAP library uses (indirectly) pthreads and + that is not compatible with PTh. + + 2. It is huge library in particular if TLS comes into play. So + problems with unfreed memory might turn up and we don't want + this in a long running daemon. + + 3. There is no easy way for timeouts. In particular the timeout + value does not work for DNS lookups (well, this is usual) and it + seems not to work while loading a large attribute like a + CRL. Having a separate process allows us to either tell the + process to commit suicide or have our own housekepping function + kill it after some time. The latter also allows proper + cancellation of a query at any point of time. + + 4. Given that we are going out to the network and usually get back + a long response, the fork/exec overhead is acceptable. + + Note that under WindowsCE the number of processes is strongly + limited (32 processes including the kernel processes) and thus we + don't use the process approach but implement a different wrapper in + ldap-wrapper-ce.c. +*/ + + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <time.h> +#include <pth.h> + +#include "dirmngr.h" +#include "exechelp.h" +#include "misc.h" +#include "ldap-wrapper.h" + + +#ifdef HAVE_W32_SYSTEM +#define setenv(a,b,c) SetEnvironmentVariable ((a),(b)) +#else +#define pth_close(fd) close(fd) +#endif + + +/* In case sysconf does not return a value we need to have a limit. */ +#ifdef _POSIX_OPEN_MAX +#define MAX_OPEN_FDS _POSIX_OPEN_MAX +#else +#define MAX_OPEN_FDS 20 +#endif + +#define INACTIVITY_TIMEOUT (opt.ldaptimeout + 60*5) /* seconds */ + + + +/* To keep track of the LDAP wrapper state we use this structure. */ +struct wrapper_context_s +{ + struct wrapper_context_s *next; + + pid_t pid; /* The pid of the wrapper process. */ + int printable_pid; /* Helper to print diagnostics after the process has + been cleaned up. */ + int fd; /* Connected with stdout of the ldap wrapper. */ + gpg_error_t fd_error; /* Set to the gpg_error of the last read error + if any. */ + int log_fd; /* Connected with stderr of the ldap wrapper. */ + pth_event_t log_ev; + ctrl_t ctrl; /* Connection data. */ + int ready; /* Internally used to mark to be removed contexts. */ + ksba_reader_t reader; /* The ksba reader object or NULL. */ + char *line; /* Used to print the log lines (malloced). */ + size_t linesize;/* Allocated size of LINE. */ + size_t linelen; /* Use size of LINE. */ + time_t stamp; /* The last time we noticed ativity. */ +}; + + + +/* We keep a global list of spawed wrapper process. A separate thread + makes use of this list to log error messages and to watch out for + finished processes. */ +static struct wrapper_context_s *wrapper_list; + +/* We need to know whether we are shutting down the process. */ +static int shutting_down; + +/* Close the pth file descriptor FD and set it to -1. */ +#define SAFE_PTH_CLOSE(fd) \ + do { int _fd = fd; if (_fd != -1) { pth_close (_fd); fd = -1;} } while (0) + + + + +/* Read a fixed amount of data from READER into BUFFER. */ +static gpg_error_t +read_buffer (ksba_reader_t reader, unsigned char *buffer, size_t count) +{ + gpg_error_t err; + size_t nread; + + while (count) + { + err = ksba_reader_read (reader, buffer, count, &nread); + if (err) + return err; + buffer += nread; + count -= nread; + } + return 0; +} + + +/* Release the wrapper context and kill a running wrapper process. */ +static void +destroy_wrapper (struct wrapper_context_s *ctx) +{ + if (ctx->pid != (pid_t)(-1)) + { + gnupg_kill_process (ctx->pid); + gnupg_release_process (ctx->pid); + } + ksba_reader_release (ctx->reader); + SAFE_PTH_CLOSE (ctx->fd); + SAFE_PTH_CLOSE (ctx->log_fd); + if (ctx->log_ev) + pth_event_free (ctx->log_ev, PTH_FREE_THIS); + xfree (ctx->line); + xfree (ctx); +} + + +/* Print the content of LINE to thye log stream but make sure to only + print complete lines. Using NULL for LINE will flush any pending + output. LINE may be modified by this fucntion. */ +static void +print_log_line (struct wrapper_context_s *ctx, char *line) +{ + char *s; + size_t n; + + if (!line) + { + if (ctx->line && ctx->linelen) + { + + log_info ("%s\n", ctx->line); + ctx->linelen = 0; + } + return; + } + + while ((s = strchr (line, '\n'))) + { + *s = 0; + if (ctx->line && ctx->linelen) + { + log_info ("%s", ctx->line); + ctx->linelen = 0; + log_printf ("%s\n", line); + } + else + log_info ("%s\n", line); + line = s + 1; + } + n = strlen (line); + if (n) + { + if (ctx->linelen + n + 1 >= ctx->linesize) + { + char *tmp; + size_t newsize; + + newsize = ctx->linesize + ((n + 255) & ~255) + 1; + tmp = (ctx->line ? xtryrealloc (ctx->line, newsize) + : xtrymalloc (newsize)); + if (!tmp) + { + log_error (_("error printing log line: %s\n"), strerror (errno)); + return; + } + ctx->line = tmp; + ctx->linesize = newsize; + } + memcpy (ctx->line + ctx->linelen, line, n); + ctx->linelen += n; + ctx->line[ctx->linelen] = 0; + } +} + + +/* Read data from the log stream. Returns true if the log stream + indicated EOF or error. */ +static int +read_log_data (struct wrapper_context_s *ctx) +{ + int n; + char line[256]; + + /* We must use the pth_read function for pipes, always. */ + do + n = pth_read (ctx->log_fd, line, sizeof line - 1); + while (n < 0 && errno == EINTR); + + if (n <= 0) /* EOF or error. */ + { + if (n < 0) + log_error (_("error reading log from ldap wrapper %d: %s\n"), + ctx->pid, strerror (errno)); + print_log_line (ctx, NULL); + SAFE_PTH_CLOSE (ctx->log_fd); + pth_event_free (ctx->log_ev, PTH_FREE_THIS); + ctx->log_ev = NULL; + return 1; + } + + line[n] = 0; + print_log_line (ctx, line); + if (ctx->stamp != (time_t)(-1)) + ctx->stamp = time (NULL); + return 0; +} + + +/* This function is run by a separate thread to maintain the list of + wrappers and to log error messages from these wrappers. */ +void * +ldap_wrapper_thread (void *dummy) +{ + int nfds; + struct wrapper_context_s *ctx; + struct wrapper_context_s *ctx_prev; + time_t current_time; + + (void)dummy; + + for (;;) + { + pth_event_t timeout_ev; + int any_action = 0; + + timeout_ev = pth_event (PTH_EVENT_TIME, pth_timeout (1, 0)); + if (! timeout_ev) + { + log_error (_("pth_event failed: %s\n"), strerror (errno)); + pth_sleep (10); + continue; + } + + for (ctx = wrapper_list; ctx; ctx = ctx->next) + { + if (ctx->log_fd != -1) + { + pth_event_isolate (ctx->log_ev); + pth_event_concat (timeout_ev, ctx->log_ev, NULL); + } + } + + /* Note that the read FDs are actually handles. Thus, we can + not use pth_select, but have to use pth_wait. */ + nfds = pth_wait (timeout_ev); + if (nfds < 0) + { + pth_event_free (timeout_ev, PTH_FREE_THIS); + log_error (_("pth_wait failed: %s\n"), strerror (errno)); + pth_sleep (10); + continue; + } + if (pth_event_status (timeout_ev) == PTH_STATUS_OCCURRED) + nfds--; + pth_event_free (timeout_ev, PTH_FREE_THIS); + + current_time = time (NULL); + if (current_time > INACTIVITY_TIMEOUT) + current_time -= INACTIVITY_TIMEOUT; + + /* Note that there is no need to lock the list because we always + add entries at the head (with a pending event status) and + thus traversing the list will even work if we have a context + switch in waitpid (which should anyway only happen with Pth's + hard system call mapping). */ + for (ctx = wrapper_list; ctx; ctx = ctx->next) + { + /* Check whether there is any logging to be done. */ + if (nfds && ctx->log_fd != -1 + && pth_event_status (ctx->log_ev) == PTH_STATUS_OCCURRED) + { + if (read_log_data (ctx)) + any_action = 1; + } + + /* Check whether the process is still running. */ + if (ctx->pid != (pid_t)(-1)) + { + gpg_error_t err; + int status; + + err = gnupg_wait_process ("[dirmngr_ldap]", ctx->pid, 0, + &status); + if (!err) + { + log_info (_("ldap wrapper %d ready"), (int)ctx->pid); + ctx->ready = 1; + gnupg_release_process (ctx->pid); + ctx->pid = (pid_t)(-1); + any_action = 1; + } + else if (gpg_err_code (err) == GPG_ERR_GENERAL) + { + if (status == 10) + log_info (_("ldap wrapper %d ready: timeout\n"), + (int)ctx->pid); + else + log_info (_("ldap wrapper %d ready: exitcode=%d\n"), + (int)ctx->pid, status); + ctx->ready = 1; + gnupg_release_process (ctx->pid); + ctx->pid = (pid_t)(-1); + any_action = 1; + } + else if (gpg_err_code (err) != GPG_ERR_TIMEOUT) + { + log_error (_("waiting for ldap wrapper %d failed: %s\n"), + (int)ctx->pid, gpg_strerror (err)); + any_action = 1; + } + } + + /* Check whether we should terminate the process. */ + if (ctx->pid != (pid_t)(-1) + && ctx->stamp != (time_t)(-1) && ctx->stamp < current_time) + { + gnupg_kill_process (ctx->pid); + ctx->stamp = (time_t)(-1); + log_info (_("ldap wrapper %d stalled - killing\n"), + (int)ctx->pid); + /* We need to close the log fd because the cleanup loop + waits for it. */ + SAFE_PTH_CLOSE (ctx->log_fd); + any_action = 1; + } + } + + /* If something has been printed to the log file or we got an + EOF from a wrapper, we now print the list of active + wrappers. */ + if (any_action && DBG_LOOKUP) + { + log_info ("ldap worker stati:\n"); + for (ctx = wrapper_list; ctx; ctx = ctx->next) + log_info (" c=%p pid=%d/%d rdr=%p ctrl=%p/%d la=%lu rdy=%d\n", + ctx, + (int)ctx->pid, (int)ctx->printable_pid, + ctx->reader, + ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0, + (unsigned long)ctx->stamp, ctx->ready); + } + + + /* Use a separate loop to check whether ready marked wrappers + may be removed. We may only do so if the ksba reader object + is not anymore in use or we are in shutdown state. */ + again: + for (ctx_prev=NULL, ctx=wrapper_list; ctx; ctx_prev=ctx, ctx=ctx->next) + if (ctx->ready + && ((ctx->log_fd == -1 && !ctx->reader) || shutting_down)) + { + if (ctx_prev) + ctx_prev->next = ctx->next; + else + wrapper_list = ctx->next; + destroy_wrapper (ctx); + /* We need to restart because destroy_wrapper might have + done a context switch. */ + goto again; + } + } + /*NOTREACHED*/ + return NULL; /* Make the compiler happy. */ +} + + + +/* Start the reaper thread for the ldap wrapper. */ +void +ldap_wrapper_launch_thread (void) +{ + static int done; + pth_attr_t tattr; + + if (done) + return; + done = 1; + + tattr = pth_attr_new(); + pth_attr_set (tattr, PTH_ATTR_JOINABLE, 0); + pth_attr_set (tattr, PTH_ATTR_STACK_SIZE, 256*1024); + pth_attr_set (tattr, PTH_ATTR_NAME, "ldap-reaper"); + + if (!pth_spawn (tattr, ldap_wrapper_thread, NULL)) + { + log_error (_("error spawning ldap wrapper reaper thread: %s\n"), + strerror (errno) ); + dirmngr_exit (1); + } + pth_attr_destroy (tattr); +} + + + + + +/* Wait until all ldap wrappers have terminated. We assume that the + kill has already been sent to all of them. */ +void +ldap_wrapper_wait_connections () +{ + shutting_down = 1; + while (wrapper_list) + pth_yield (NULL); +} + + +/* This function is to be used to release a context associated with the + given reader object. */ +void +ldap_wrapper_release_context (ksba_reader_t reader) +{ + struct wrapper_context_s *ctx; + + if (!reader ) + return; + + for (ctx=wrapper_list; ctx; ctx=ctx->next) + if (ctx->reader == reader) + { + if (DBG_LOOKUP) + log_info ("releasing ldap worker c=%p pid=%d/%d rdr=%p ctrl=%p/%d\n", + ctx, + (int)ctx->pid, (int)ctx->printable_pid, + ctx->reader, + ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0); + + ctx->reader = NULL; + SAFE_PTH_CLOSE (ctx->fd); + if (ctx->ctrl) + { + ctx->ctrl->refcount--; + ctx->ctrl = NULL; + } + if (ctx->fd_error) + log_info (_("reading from ldap wrapper %d failed: %s\n"), + ctx->printable_pid, gpg_strerror (ctx->fd_error)); + break; + } +} + +/* Cleanup all resources held by the connection associated with + CTRL. This is used after a cancel to kill running wrappers. */ +void +ldap_wrapper_connection_cleanup (ctrl_t ctrl) +{ + struct wrapper_context_s *ctx; + + for (ctx=wrapper_list; ctx; ctx=ctx->next) + if (ctx->ctrl && ctx->ctrl == ctrl) + { + ctx->ctrl->refcount--; + ctx->ctrl = NULL; + if (ctx->pid != (pid_t)(-1)) + gnupg_kill_process (ctx->pid); + if (ctx->fd_error) + log_info (_("reading from ldap wrapper %d failed: %s\n"), + ctx->printable_pid, gpg_strerror (ctx->fd_error)); + } +} + +/* This is the callback used by the ldap wrapper to feed the ksba + reader with the wrappers stdout. See the description of + ksba_reader_set_cb for details. */ +static int +reader_callback (void *cb_value, char *buffer, size_t count, size_t *nread) +{ + struct wrapper_context_s *ctx = cb_value; + size_t nleft = count; + + /* FIXME: We might want to add some internal buffering because the + ksba code does not do any buffering for itself (because a ksba + reader may be detached from another stream to read other data and + the it would be cumbersome to get back already buffered + stuff). */ + + if (!buffer && !count && !nread) + return -1; /* Rewind is not supported. */ + + /* If we ever encountered a read error don't allow to continue and + possible overwrite the last error cause. Bail out also if the + file descriptor has been closed. */ + if (ctx->fd_error || ctx->fd == -1) + { + *nread = 0; + return -1; + } + + while (nleft > 0) + { + int n; + pth_event_t evt; + gpg_error_t err; + + evt = pth_event (PTH_EVENT_TIME, pth_timeout (1, 0)); + n = pth_read_ev (ctx->fd, buffer, nleft, evt); + if (n < 0 && evt && pth_event_occurred (evt)) + { + n = 0; + err = dirmngr_tick (ctx->ctrl); + if (err) + { + ctx->fd_error = err; + SAFE_PTH_CLOSE (ctx->fd); + if (evt) + pth_event_free (evt, PTH_FREE_THIS); + return -1; + } + + } + else if (n < 0) + { + ctx->fd_error = gpg_error_from_errno (errno); + SAFE_PTH_CLOSE (ctx->fd); + if (evt) + pth_event_free (evt, PTH_FREE_THIS); + return -1; + } + else if (!n) + { + if (nleft == count) + { + if (evt) + pth_event_free (evt, PTH_FREE_THIS); + return -1; /* EOF. */ + } + break; + } + nleft -= n; + buffer += n; + if (evt) + pth_event_free (evt, PTH_FREE_THIS); + if (n > 0 && ctx->stamp != (time_t)(-1)) + ctx->stamp = time (NULL); + } + *nread = count - nleft; + + return 0; + +} + +/* Fork and exec the LDAP wrapper and returns a new libksba reader + object at READER. ARGV is a NULL terminated list of arguments for + the wrapper. The function returns 0 on success or an error code. + + Special hack to avoid passing a password through the command line + which is globally visible: If the first element of ARGV is "--pass" + it will be removed and instead the environment variable + DIRMNGR_LDAP_PASS will be set to the next value of ARGV. On modern + OSes the environment is not visible to other users. For those old + systems where it can't be avoided, we don't want to go into the + hassle of passing the password via stdin; it's just too complicated + and an LDAP password used for public directory lookups should not + be that confidential. */ +gpg_error_t +ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, const char *argv[]) +{ + gpg_error_t err; + pid_t pid; + struct wrapper_context_s *ctx; + int i; + int j; + const char **arg_list; + const char *pgmname; + int outpipe[2], errpipe[2]; + + /* It would be too simple to connect stderr just to our logging + stream. The problem is that if we are running multi-threaded + everything gets intermixed. Clearly we don't want this. So the + only viable solutions are either to have another thread + responsible for logging the messages or to add an option to the + wrapper module to do the logging on its own. Given that we anyway + need a way to rip the child process and this is best done using a + general ripping thread, that thread can do the logging too. */ + + *reader = NULL; + + /* Files: We need to prepare stdin and stdout. We get stderr from + the function. */ + if (!opt.ldap_wrapper_program || !*opt.ldap_wrapper_program) + pgmname = gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR_LDAP); + else + pgmname = opt.ldap_wrapper_program; + + /* Create command line argument array. */ + for (i = 0; argv[i]; i++) + ; + arg_list = xtrycalloc (i + 2, sizeof *arg_list); + if (!arg_list) + { + err = gpg_error_from_syserror (); + log_error (_("error allocating memory: %s\n"), strerror (errno)); + return err; + } + for (i = j = 0; argv[i]; i++, j++) + if (!i && argv[i + 1] && !strcmp (*argv, "--pass")) + { + arg_list[j] = "--env-pass"; + setenv ("DIRMNGR_LDAP_PASS", argv[1], 1); + i++; + } + else + arg_list[j] = (char*) argv[i]; + + ctx = xtrycalloc (1, sizeof *ctx); + if (!ctx) + { + err = gpg_error_from_syserror (); + log_error (_("error allocating memory: %s\n"), strerror (errno)); + xfree (arg_list); + return err; + } + + err = gnupg_create_inbound_pipe (outpipe); + if (!err) + { + err = gnupg_create_inbound_pipe (errpipe); + if (err) + { + close (outpipe[0]); + close (outpipe[1]); + } + } + if (err) + { + log_error (_("error creating pipe: %s\n"), gpg_strerror (err)); + xfree (arg_list); + xfree (ctx); + return err; + } + + err = gnupg_spawn_process_fd (pgmname, arg_list, + -1, outpipe[1], errpipe[1], &pid); + xfree (arg_list); + close (outpipe[1]); + close (errpipe[1]); + if (err) + { + close (outpipe[0]); + close (errpipe[0]); + xfree (ctx); + return err; + } + + ctx->pid = pid; + ctx->printable_pid = (int) pid; + ctx->fd = outpipe[0]; + ctx->log_fd = errpipe[0]; + ctx->log_ev = pth_event (PTH_EVENT_FD | PTH_UNTIL_FD_READABLE, ctx->log_fd); + if (! ctx->log_ev) + { + xfree (ctx); + return gpg_error_from_syserror (); + } + ctx->ctrl = ctrl; + ctrl->refcount++; + ctx->stamp = time (NULL); + + err = ksba_reader_new (reader); + if (!err) + err = ksba_reader_set_cb (*reader, reader_callback, ctx); + if (err) + { + log_error (_("error initializing reader object: %s\n"), + gpg_strerror (err)); + destroy_wrapper (ctx); + ksba_reader_release (*reader); + *reader = NULL; + return err; + } + + /* Hook the context into our list of running wrappers. */ + ctx->reader = *reader; + ctx->next = wrapper_list; + wrapper_list = ctx; + if (opt.verbose) + log_info ("ldap wrapper %d started (reader %p)\n", + (int)ctx->pid, ctx->reader); + + /* Need to wait for the first byte so we are able to detect an empty + output and not let the consumer see an EOF without further error + indications. The CRL loading logic assumes that after return + from this function, a failed search (e.g. host not found ) is + indicated right away. */ + { + unsigned char c; + + err = read_buffer (*reader, &c, 1); + if (err) + { + ldap_wrapper_release_context (*reader); + ksba_reader_release (*reader); + *reader = NULL; + if (gpg_err_code (err) == GPG_ERR_EOF) + return gpg_error (GPG_ERR_NO_DATA); + else + return err; + } + ksba_reader_unread (*reader, &c, 1); + } + + return 0; +} diff --git a/dirmngr/ldap-wrapper.h b/dirmngr/ldap-wrapper.h new file mode 100644 index 000000000..dfe55eb61 --- /dev/null +++ b/dirmngr/ldap-wrapper.h @@ -0,0 +1,33 @@ +/* ldap-wrapper.h - Interface to an LDAP access wrapper. + * Copyright (C) 2010 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 LDAP_WRAPPER_H +#define LDAP_WRAPPER_H + +void ldap_wrapper_launch_thread (void); +void ldap_wrapper_wait_connections (void); +void ldap_wrapper_release_context (ksba_reader_t reader); +void ldap_wrapper_connection_cleanup (ctrl_t); +gpg_error_t ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, + const char *argv[]); + + + + +#endif /*LDAP_WRAPPER_H*/ diff --git a/dirmngr/ldap.c b/dirmngr/ldap.c index fd3c3f510..b71a0d3c9 100644 --- a/dirmngr/ldap.c +++ b/dirmngr/ldap.c @@ -35,22 +35,8 @@ #include "crlfetch.h" #include "ldapserver.h" #include "misc.h" +#include "ldap-wrapper.h" -#ifdef HAVE_W32_SYSTEM -#define setenv(a,b,c) SetEnvironmentVariable ((a),(b)) -#else -#define pth_close(fd) close(fd) -#endif - - -/* In case sysconf does not return a value we need to have a limit. */ -#ifdef _POSIX_OPEN_MAX -#define MAX_OPEN_FDS _POSIX_OPEN_MAX -#else -#define MAX_OPEN_FDS 20 -#endif - -#define INACTIVITY_TIMEOUT (opt.ldaptimeout + 60*5) /* seconds */ #define UNENCODED_URL_CHARS "abcdefghijklmnopqrstuvwxyz" \ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ @@ -72,50 +58,6 @@ struct cert_fetch_context_s }; -/* To keep track of the LDAP wrapper state we use this structure. */ -struct wrapper_context_s -{ - struct wrapper_context_s *next; - - pid_t pid; /* The pid of the wrapper process. */ - int printable_pid; /* Helper to print diagnostics after the process has - been cleaned up. */ - int fd; /* Connected with stdout of the ldap wrapper. */ - gpg_error_t fd_error; /* Set to the gpg_error of the last read error - if any. */ - int log_fd; /* Connected with stderr of the ldap wrapper. */ - pth_event_t log_ev; - ctrl_t ctrl; /* Connection data. */ - int ready; /* Internally used to mark to be removed contexts. */ - ksba_reader_t reader; /* The ksba reader object or NULL. */ - char *line; /* Used to print the log lines (malloced). */ - size_t linesize;/* Allocated size of LINE. */ - size_t linelen; /* Use size of LINE. */ - time_t stamp; /* The last time we noticed ativity. */ -}; - - - - - -/* We keep a global list of spawed wrapper process. A separate thread - makes use of this list to log error messages and to watch out for - finished processes. */ -static struct wrapper_context_s *wrapper_list; - -/* We need to know whether we are shutting down the process. */ -static int shutting_down; - -/* Close the pth file descriptor FD and set it to -1. */ -#define SAFE_PTH_CLOSE(fd) \ - do { int _fd = fd; if (_fd != -1) { pth_close (_fd); fd = -1;} } while (0) - - -/* Prototypes. */ -static gpg_error_t read_buffer (ksba_reader_t reader, - unsigned char *buffer, size_t count); - - /* Add HOST and PORT to our list of LDAP servers. Fixme: We should @@ -165,604 +107,6 @@ add_server_to_servers (const char *host, int port) } -/* Release the wrapper context and kill a running wrapper process. */ -static void -destroy_wrapper (struct wrapper_context_s *ctx) -{ - if (ctx->pid != (pid_t)(-1)) - { - gnupg_kill_process (ctx->pid); - gnupg_release_process (ctx->pid); - } - ksba_reader_release (ctx->reader); - SAFE_PTH_CLOSE (ctx->fd); - SAFE_PTH_CLOSE (ctx->log_fd); - if (ctx->log_ev) - pth_event_free (ctx->log_ev, PTH_FREE_THIS); - xfree (ctx->line); - xfree (ctx); -} - - -/* Print the content of LINE to thye log stream but make sure to only - print complete lines. Using NULL for LINE will flush any pending - output. LINE may be modified by this fucntion. */ -static void -print_log_line (struct wrapper_context_s *ctx, char *line) -{ - char *s; - size_t n; - - if (!line) - { - if (ctx->line && ctx->linelen) - { - - log_info ("%s\n", ctx->line); - ctx->linelen = 0; - } - return; - } - - while ((s = strchr (line, '\n'))) - { - *s = 0; - if (ctx->line && ctx->linelen) - { - log_info ("%s", ctx->line); - ctx->linelen = 0; - log_printf ("%s\n", line); - } - else - log_info ("%s\n", line); - line = s + 1; - } - n = strlen (line); - if (n) - { - if (ctx->linelen + n + 1 >= ctx->linesize) - { - char *tmp; - size_t newsize; - - newsize = ctx->linesize + ((n + 255) & ~255) + 1; - tmp = (ctx->line ? xtryrealloc (ctx->line, newsize) - : xtrymalloc (newsize)); - if (!tmp) - { - log_error (_("error printing log line: %s\n"), strerror (errno)); - return; - } - ctx->line = tmp; - ctx->linesize = newsize; - } - memcpy (ctx->line + ctx->linelen, line, n); - ctx->linelen += n; - ctx->line[ctx->linelen] = 0; - } -} - - -/* Read data from the log stream. Returns true if the log stream - indicated EOF or error. */ -static int -read_log_data (struct wrapper_context_s *ctx) -{ - int n; - char line[256]; - - /* We must use the pth_read function for pipes, always. */ - do - n = pth_read (ctx->log_fd, line, sizeof line - 1); - while (n < 0 && errno == EINTR); - - if (n <= 0) /* EOF or error. */ - { - if (n < 0) - log_error (_("error reading log from ldap wrapper %d: %s\n"), - ctx->pid, strerror (errno)); - print_log_line (ctx, NULL); - SAFE_PTH_CLOSE (ctx->log_fd); - pth_event_free (ctx->log_ev, PTH_FREE_THIS); - ctx->log_ev = NULL; - return 1; - } - - line[n] = 0; - print_log_line (ctx, line); - if (ctx->stamp != (time_t)(-1)) - ctx->stamp = time (NULL); - return 0; -} - - -/* This function is run by a separate thread to maintain the list of - wrappers and to log error messages from these wrappers. */ -void * -ldap_wrapper_thread (void *dummy) -{ - int nfds; - struct wrapper_context_s *ctx; - struct wrapper_context_s *ctx_prev; - time_t current_time; - - (void)dummy; - - for (;;) - { - pth_event_t timeout_ev; - int any_action = 0; - - timeout_ev = pth_event (PTH_EVENT_TIME, pth_timeout (1, 0)); - if (! timeout_ev) - { - log_error (_("pth_event failed: %s\n"), strerror (errno)); - pth_sleep (10); - continue; - } - - for (ctx = wrapper_list; ctx; ctx = ctx->next) - { - if (ctx->log_fd != -1) - { - pth_event_isolate (ctx->log_ev); - pth_event_concat (timeout_ev, ctx->log_ev, NULL); - } - } - - /* Note that the read FDs are actually handles. Thus, we can - not use pth_select, but have to use pth_wait. */ - nfds = pth_wait (timeout_ev); - if (nfds < 0) - { - pth_event_free (timeout_ev, PTH_FREE_THIS); - log_error (_("pth_wait failed: %s\n"), strerror (errno)); - pth_sleep (10); - continue; - } - if (pth_event_status (timeout_ev) == PTH_STATUS_OCCURRED) - nfds--; - pth_event_free (timeout_ev, PTH_FREE_THIS); - - current_time = time (NULL); - if (current_time > INACTIVITY_TIMEOUT) - current_time -= INACTIVITY_TIMEOUT; - - /* Note that there is no need to lock the list because we always - add entries at the head (with a pending event status) and - thus traversing the list will even work if we have a context - switch in waitpid (which should anyway only happen with Pth's - hard system call mapping). */ - for (ctx = wrapper_list; ctx; ctx = ctx->next) - { - /* Check whether there is any logging to be done. */ - if (nfds && ctx->log_fd != -1 - && pth_event_status (ctx->log_ev) == PTH_STATUS_OCCURRED) - { - if (read_log_data (ctx)) - any_action = 1; - } - - /* Check whether the process is still running. */ - if (ctx->pid != (pid_t)(-1)) - { - gpg_error_t err; - int status; - - err = gnupg_wait_process ("[dirmngr_ldap]", ctx->pid, 0, - &status); - if (!err) - { - log_info (_("ldap wrapper %d ready"), (int)ctx->pid); - ctx->ready = 1; - gnupg_release_process (ctx->pid); - ctx->pid = (pid_t)(-1); - any_action = 1; - } - else if (gpg_err_code (err) == GPG_ERR_GENERAL) - { - if (status == 10) - log_info (_("ldap wrapper %d ready: timeout\n"), - (int)ctx->pid); - else - log_info (_("ldap wrapper %d ready: exitcode=%d\n"), - (int)ctx->pid, status); - ctx->ready = 1; - gnupg_release_process (ctx->pid); - ctx->pid = (pid_t)(-1); - any_action = 1; - } - else if (gpg_err_code (err) != GPG_ERR_TIMEOUT) - { - log_error (_("waiting for ldap wrapper %d failed: %s\n"), - (int)ctx->pid, gpg_strerror (err)); - any_action = 1; - } - } - - /* Check whether we should terminate the process. */ - if (ctx->pid != (pid_t)(-1) - && ctx->stamp != (time_t)(-1) && ctx->stamp < current_time) - { - gnupg_kill_process (ctx->pid); - ctx->stamp = (time_t)(-1); - log_info (_("ldap wrapper %d stalled - killing\n"), - (int)ctx->pid); - /* We need to close the log fd because the cleanup loop - waits for it. */ - SAFE_PTH_CLOSE (ctx->log_fd); - any_action = 1; - } - } - - /* If something has been printed to the log file or we got an - EOF from a wrapper, we now print the list of active - wrappers. */ - if (any_action && DBG_LOOKUP) - { - log_info ("ldap worker stati:\n"); - for (ctx = wrapper_list; ctx; ctx = ctx->next) - log_info (" c=%p pid=%d/%d rdr=%p ctrl=%p/%d la=%lu rdy=%d\n", - ctx, - (int)ctx->pid, (int)ctx->printable_pid, - ctx->reader, - ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0, - (unsigned long)ctx->stamp, ctx->ready); - } - - - /* Use a separate loop to check whether ready marked wrappers - may be removed. We may only do so if the ksba reader object - is not anymore in use or we are in shutdown state. */ - again: - for (ctx_prev=NULL, ctx=wrapper_list; ctx; ctx_prev=ctx, ctx=ctx->next) - if (ctx->ready - && ((ctx->log_fd == -1 && !ctx->reader) || shutting_down)) - { - if (ctx_prev) - ctx_prev->next = ctx->next; - else - wrapper_list = ctx->next; - destroy_wrapper (ctx); - /* We need to restart because destroy_wrapper might have - done a context switch. */ - goto again; - } - } - /*NOTREACHED*/ - return NULL; /* Make the compiler happy. */ -} - - - -/* Wait until all ldap wrappers have terminated. We assume that the - kill has already been sent to all of them. */ -void -ldap_wrapper_wait_connections () -{ - shutting_down = 1; - while (wrapper_list) - pth_yield (NULL); -} - - -/* This function is to be used to release a context associated with the - given reader object. */ -void -ldap_wrapper_release_context (ksba_reader_t reader) -{ - struct wrapper_context_s *ctx; - - if (!reader ) - return; - - for (ctx=wrapper_list; ctx; ctx=ctx->next) - if (ctx->reader == reader) - { - if (DBG_LOOKUP) - log_info ("releasing ldap worker c=%p pid=%d/%d rdr=%p ctrl=%p/%d\n", - ctx, - (int)ctx->pid, (int)ctx->printable_pid, - ctx->reader, - ctx->ctrl, ctx->ctrl? ctx->ctrl->refcount:0); - - ctx->reader = NULL; - SAFE_PTH_CLOSE (ctx->fd); - if (ctx->ctrl) - { - ctx->ctrl->refcount--; - ctx->ctrl = NULL; - } - if (ctx->fd_error) - log_info (_("reading from ldap wrapper %d failed: %s\n"), - ctx->printable_pid, gpg_strerror (ctx->fd_error)); - break; - } -} - -/* Cleanup all resources held by the connection associated with - CTRL. This is used after a cancel to kill running wrappers. */ -void -ldap_wrapper_connection_cleanup (ctrl_t ctrl) -{ - struct wrapper_context_s *ctx; - - for (ctx=wrapper_list; ctx; ctx=ctx->next) - if (ctx->ctrl && ctx->ctrl == ctrl) - { - ctx->ctrl->refcount--; - ctx->ctrl = NULL; - if (ctx->pid != (pid_t)(-1)) - gnupg_kill_process (ctx->pid); - if (ctx->fd_error) - log_info (_("reading from ldap wrapper %d failed: %s\n"), - ctx->printable_pid, gpg_strerror (ctx->fd_error)); - } -} - -/* This is the callback used by the ldap wrapper to feed the ksba - reader with the wrappers stdout. See the description of - ksba_reader_set_cb for details. */ -static int -reader_callback (void *cb_value, char *buffer, size_t count, size_t *nread) -{ - struct wrapper_context_s *ctx = cb_value; - size_t nleft = count; - - /* FIXME: We might want to add some internal buffering because the - ksba code does not do any buffering for itself (because a ksba - reader may be detached from another stream to read other data and - the it would be cumbersome to get back already buffered - stuff). */ - - if (!buffer && !count && !nread) - return -1; /* Rewind is not supported. */ - - /* If we ever encountered a read error don't allow to continue and - possible overwrite the last error cause. Bail out also if the - file descriptor has been closed. */ - if (ctx->fd_error || ctx->fd == -1) - { - *nread = 0; - return -1; - } - - while (nleft > 0) - { - int n; - pth_event_t evt; - gpg_error_t err; - - evt = pth_event (PTH_EVENT_TIME, pth_timeout (1, 0)); - n = pth_read_ev (ctx->fd, buffer, nleft, evt); - if (n < 0 && evt && pth_event_occurred (evt)) - { - n = 0; - err = dirmngr_tick (ctx->ctrl); - if (err) - { - ctx->fd_error = err; - SAFE_PTH_CLOSE (ctx->fd); - if (evt) - pth_event_free (evt, PTH_FREE_THIS); - return -1; - } - - } - else if (n < 0) - { - ctx->fd_error = gpg_error_from_errno (errno); - SAFE_PTH_CLOSE (ctx->fd); - if (evt) - pth_event_free (evt, PTH_FREE_THIS); - return -1; - } - else if (!n) - { - if (nleft == count) - { - if (evt) - pth_event_free (evt, PTH_FREE_THIS); - return -1; /* EOF. */ - } - break; - } - nleft -= n; - buffer += n; - if (evt) - pth_event_free (evt, PTH_FREE_THIS); - if (n > 0 && ctx->stamp != (time_t)(-1)) - ctx->stamp = time (NULL); - } - *nread = count - nleft; - - return 0; - -} - -/* Fork and exec the LDAP wrapper and returns a new libksba reader - object at READER. ARGV is a NULL terminated list of arguments for - the wrapper. The function returns 0 on success or an error code. - - We can't use LDAP directly for these reasons: - - 1. On some systems the LDAP library uses (indirectly) pthreads and - that is not compatible with PTh. - - 2. It is huge library in particular if TLS comes into play. So - problems with unfreed memory might turn up and we don't want - this in a long running daemon. - - 3. There is no easy way for timeouts. In particular the timeout - value does not work for DNS lookups (well, this is usual) and it - seems not to work while loading a large attribute like a - CRL. Having a separate process allows us to either tell the - process to commit suicide or have our own housekepping function - kill it after some time. The latter also allows proper - cancellation of a query at any point of time. - - 4. Given that we are going out to the network and usually get back - a long response, the fork/exec overhead is acceptable. - - Special hack to avoid passing a password through the command line - which is globally visible: If the first element of ARGV is "--pass" - it will be removed and instead the environment variable - DIRMNGR_LDAP_PASS will be set to the next value of ARGV. On modern - OSes the environment is not visible to other users. For those old - systems where it can't be avoided, we don't want to go into the - hassle of passing the password via stdin; it's just too complicated - and an LDAP password used for public directory lookups should not - be that confidential. */ -static gpg_error_t -ldap_wrapper (ctrl_t ctrl, ksba_reader_t *reader, const char *argv[]) -{ - gpg_error_t err; - pid_t pid; - struct wrapper_context_s *ctx; - int i; - int j; - const char **arg_list; - const char *pgmname; - int outpipe[2], errpipe[2]; - - /* It would be too simple to connect stderr just to our logging - stream. The problem is that if we are running multi-threaded - everything gets intermixed. Clearly we don't want this. So the - only viable solutions are either to have another thread - responsible for logging the messages or to add an option to the - wrapper module to do the logging on its own. Given that we anyway - need a way to rip the child process and this is best done using a - general ripping thread, that thread can do the logging too. */ - - *reader = NULL; - - /* Files: We need to prepare stdin and stdout. We get stderr from - the function. */ - if (!opt.ldap_wrapper_program || !*opt.ldap_wrapper_program) - pgmname = gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR_LDAP); - else - pgmname = opt.ldap_wrapper_program; - - /* Create command line argument array. */ - for (i = 0; argv[i]; i++) - ; - arg_list = xtrycalloc (i + 2, sizeof *arg_list); - if (!arg_list) - { - err = gpg_error_from_syserror (); - log_error (_("error allocating memory: %s\n"), strerror (errno)); - return err; - } - for (i = j = 0; argv[i]; i++, j++) - if (!i && argv[i + 1] && !strcmp (*argv, "--pass")) - { - arg_list[j] = "--env-pass"; - setenv ("DIRMNGR_LDAP_PASS", argv[1], 1); - i++; - } - else - arg_list[j] = (char*) argv[i]; - - ctx = xtrycalloc (1, sizeof *ctx); - if (!ctx) - { - err = gpg_error_from_syserror (); - log_error (_("error allocating memory: %s\n"), strerror (errno)); - xfree (arg_list); - return err; - } - - err = gnupg_create_inbound_pipe (outpipe); - if (!err) - { - err = gnupg_create_inbound_pipe (errpipe); - if (err) - { - close (outpipe[0]); - close (outpipe[1]); - } - } - if (err) - { - log_error (_("error creating pipe: %s\n"), gpg_strerror (err)); - xfree (arg_list); - xfree (ctx); - return err; - } - - err = gnupg_spawn_process_fd (pgmname, arg_list, - -1, outpipe[1], errpipe[1], &pid); - xfree (arg_list); - close (outpipe[1]); - close (errpipe[1]); - if (err) - { - close (outpipe[0]); - close (errpipe[0]); - xfree (ctx); - return err; - } - - ctx->pid = pid; - ctx->printable_pid = (int) pid; - ctx->fd = outpipe[0]; - ctx->log_fd = errpipe[0]; - ctx->log_ev = pth_event (PTH_EVENT_FD | PTH_UNTIL_FD_READABLE, ctx->log_fd); - if (! ctx->log_ev) - { - xfree (ctx); - return gpg_error_from_syserror (); - } - ctx->ctrl = ctrl; - ctrl->refcount++; - ctx->stamp = time (NULL); - - err = ksba_reader_new (reader); - if (!err) - err = ksba_reader_set_cb (*reader, reader_callback, ctx); - if (err) - { - log_error (_("error initializing reader object: %s\n"), - gpg_strerror (err)); - destroy_wrapper (ctx); - ksba_reader_release (*reader); - *reader = NULL; - return err; - } - - /* Hook the context into our list of running wrappers. */ - ctx->reader = *reader; - ctx->next = wrapper_list; - wrapper_list = ctx; - if (opt.verbose) - log_info ("ldap wrapper %d started (reader %p)\n", - (int)ctx->pid, ctx->reader); - - /* Need to wait for the first byte so we are able to detect an empty - output and not let the consumer see an EOF without further error - indications. The CRL loading logic assumes that after return - from this function, a failed search (e.g. host not found ) is - indicated right away. */ - { - unsigned char c; - - err = read_buffer (*reader, &c, 1); - if (err) - { - ldap_wrapper_release_context (*reader); - ksba_reader_release (*reader); - *reader = NULL; - if (gpg_err_code (err) == GPG_ERR_EOF) - return gpg_error (GPG_ERR_NO_DATA); - else - return err; - } - ksba_reader_unread (*reader, &c, 1); - } - - return 0; -} - /* Perform an LDAP query. Returns an gpg error code or 0 on success. diff --git a/dirmngr/ldapserver.h b/dirmngr/ldapserver.h index eb1ab226d..6e5f163f4 100644 --- a/dirmngr/ldapserver.h +++ b/dirmngr/ldapserver.h @@ -27,8 +27,8 @@ void ldapserver_list_free (ldap_server_t servers); /* Parse a single LDAP server configuration line. Returns the server - or NULL in case of errors. The configuration lineis assumed to be - colon seprated with these fields: + or NULL in case of errors. The configuration line is assumed to be + colon separated with these fields: 1. field: Hostname 2. field: Portnumber |