diff options
Diffstat (limited to 'agent/gpg-agent.c')
-rw-r--r-- | agent/gpg-agent.c | 453 |
1 files changed, 272 insertions, 181 deletions
diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 1db422737..b7863cbbe 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -341,15 +341,13 @@ static struct debug_flags_s debug_flags [] = #define MIN_PASSPHRASE_NONALPHA (1) #define MAX_PASSPHRASE_DAYS (0) -/* The timer tick used for housekeeping stuff. Note that on Windows - * we use a SetWaitableTimer seems to signal earlier than about 2 - * seconds. Thus we use 4 seconds on all platforms. - * CHECK_OWN_SOCKET_INTERVAL defines how often we check - * our own socket in standard socket mode. If that value is 0 we - * don't check at all. All values are in seconds. */ -#define TIMERTICK_INTERVAL (4) +/* CHECK_OWN_SOCKET_INTERVAL defines how often we check our own socket + * in standard socket mode. If that value is 0 we don't check at all. + * Values is in seconds. */ #define CHECK_OWN_SOCKET_INTERVAL (60) - +/* CHECK_PROBLEMS_INTERFAL defines how often we check the existence of + * parent process and homedir. Value is in seconds. */ +#define CHECK_PROBLEMS_INTERVAL (4) /* Flag indicating that the ssh-agent subsystem has been enabled. */ static int ssh_support; @@ -384,9 +382,6 @@ static int startup_signal_mask_valid; /* Flag to indicate that a shutdown was requested. */ static int shutdown_pending; -/* Counter for the currently running own socket checks. */ -static int check_own_socket_running; - /* Flags to indicate that check_own_socket shall not be called. */ static int disable_check_own_socket; @@ -396,6 +391,12 @@ static int is_supervised; /* Flag indicating to start the daemon even if one already runs. */ static int steal_socket; +/* Flag to monitor problems. */ +static int problem_detected; +#define AGENT_PROBLEM_SOCKET_TAKEOVER (1 << 0) +#define AGENT_PROBLEM_PARENT_HAS_GONE (1 << 1) +#define AGENT_PROBLEM_HOMEDIR_REMOVED (1 << 2) + /* Flag to inhibit socket removal in cleanup. */ static int inhibit_socket_removal; @@ -432,6 +433,17 @@ static assuan_sock_nonce_t socket_nonce_ssh; * Let's try this as default. Change at runtime with --listen-backlog. */ static int listen_backlog = 64; +#ifdef HAVE_W32_SYSTEM +/* The event to break the select call. */ +static HANDLE the_event2; +#elif defined(HAVE_PSELECT_NO_EINTR) +/* An FD to break the select call. */ +static int event_pipe_fd; +#else +/* PID of the main thread. */ +static pid_t main_thread_pid; +#endif + /* Default values for options passed to the pinentry. */ static char *default_display; static char *default_ttyname; @@ -452,9 +464,14 @@ static const char *debug_level; the log file after a SIGHUP if it didn't changed. Malloced. */ static char *current_logfile; -/* The handle_tick() function may test whether a parent is still - * running. We record the PID of the parent here or -1 if it should - * be watched. */ +#ifdef HAVE_W32_SYSTEM +#define HAVE_PARENT_PID_SUPPORT 0 +#else +#define HAVE_PARENT_PID_SUPPORT 1 +#endif +/* The check_others_thread() function may test whether a parent is + * still running. We record the PID of the parent here or -1 if it + * should be watched. */ static pid_t parent_pid = (pid_t)(-1); /* This flag is true if the inotify mechanism for detecting the @@ -517,12 +534,11 @@ static void handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t listen_fd_extra, gnupg_fd_t listen_fd_browser, gnupg_fd_t listen_fd_ssh); -static void check_own_socket (void); static int check_for_running_agent (int silent); - -/* Pth wrapper function definitions. */ -ASSUAN_SYSTEM_NPTH_IMPL; - +#if CHECK_OWN_SOCKET_INTERVAL > 0 +static void *check_own_socket_thread (void *arg); +#endif +static void *check_others_thread (void *arg); /* Functions. @@ -1046,6 +1062,7 @@ thread_init_once (void) * initialized and thus Libgcrypt could not set its system call * clamp. */ gcry_control (GCRYCTL_REINIT_SYSCALL_CLAMP, 0, 0); + assuan_control (ASSUAN_CONTROL_REINIT_SYSCALL_CLAMP, NULL); } @@ -1053,7 +1070,6 @@ static void initialize_modules (void) { thread_init_once (); - assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); initialize_module_cache (); initialize_module_call_pinentry (); initialize_module_daemon (); @@ -1113,7 +1129,6 @@ main (int argc, char **argv) assuan_set_malloc_hooks (&malloc_hooks); assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); assuan_sock_init (); - assuan_sock_set_system_hooks (ASSUAN_SYSTEM_NPTH); setup_libassuan_logging (&opt.debug, NULL); setup_libgcrypt_logging (); @@ -2134,39 +2149,45 @@ get_agent_active_connection_count (void) notification event. Calling it the first time creates that event. */ #if defined(HAVE_W32_SYSTEM) +static void * +create_an_event (void) +{ + HANDLE h, h2; + SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE}; + + /* We need to use a manual reset event object due to the way our + w32-pth wait function works: If we would use an automatic + reset event we are not able to figure out which handle has + been signaled because at the time we single out the signaled + handles using WFSO the event has already been reset due to + the WFMO. */ + h = CreateEvent (&sa, TRUE, FALSE, NULL); + if (!h) + log_error ("can't create an event: %s\n", w32_strerror (-1) ); + else if (!DuplicateHandle (GetCurrentProcess(), h, + GetCurrentProcess(), &h2, + EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0)) + { + log_error ("setting synchronize for an event failed: %s\n", + w32_strerror (-1) ); + CloseHandle (h); + } + else + { + CloseHandle (h); + return h2; + } + + return INVALID_HANDLE_VALUE; +} + void * get_agent_daemon_notify_event (void) { static HANDLE the_event = INVALID_HANDLE_VALUE; if (the_event == INVALID_HANDLE_VALUE) - { - HANDLE h, h2; - SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE}; - - /* We need to use a manual reset event object due to the way our - w32-pth wait function works: If we would use an automatic - reset event we are not able to figure out which handle has - been signaled because at the time we single out the signaled - handles using WFSO the event has already been reset due to - the WFMO. */ - h = CreateEvent (&sa, TRUE, FALSE, NULL); - if (!h) - log_error ("can't create scd notify event: %s\n", w32_strerror (-1) ); - else if (!DuplicateHandle (GetCurrentProcess(), h, - GetCurrentProcess(), &h2, - EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0)) - { - log_error ("setting synchronize for scd notify event failed: %s\n", - w32_strerror (-1) ); - CloseHandle (h); - } - else - { - CloseHandle (h); - the_event = h2; - } - } + the_event = create_an_event (); return the_event; } @@ -2423,57 +2444,6 @@ create_directories (void) } - -/* This is the worker for the ticker. It is called every few seconds - and may only do fast operations. */ -static void -handle_tick (void) -{ - static time_t last_minute; - struct stat statbuf; - - if (!last_minute) - last_minute = time (NULL); - - /* If we are running as a child of another process, check whether - the parent is still alive and shutdown if not. */ -#ifndef HAVE_W32_SYSTEM - if (parent_pid != (pid_t)(-1)) - { - if (kill (parent_pid, 0)) - { - shutdown_pending = 2; - log_info ("parent process died - shutting down\n"); - log_info ("%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13)); - cleanup (); - agent_exit (0); - } - } -#endif /*HAVE_W32_SYSTEM*/ - - /* Code to be run from time to time. */ -#if CHECK_OWN_SOCKET_INTERVAL > 0 - if (last_minute + CHECK_OWN_SOCKET_INTERVAL <= time (NULL)) - { - check_own_socket (); - last_minute = time (NULL); - } -#endif - - /* Need to check for expired cache entries. */ - agent_cache_housekeeping (); - - /* Check whether the homedir is still available. */ - if (!shutdown_pending - && (!have_homedir_inotify || !reliable_homedir_inotify) - && gnupg_stat (gnupg_homedir (), &statbuf) && errno == ENOENT) - { - shutdown_pending = 1; - log_info ("homedir has been removed - shutting down\n"); - } -} - - /* A global function which allows us to call the reload stuff from other places too. This is only used when build for W32. */ void @@ -2532,6 +2502,11 @@ handle_signal (int signo) agent_sigusr2_action (); break; + case SIGCONT: + /* Do nothing, but break the syscall. */ + log_debug ("SIGCONT received - breaking select\n"); + break; + case SIGTERM: if (!shutdown_pending) log_info ("SIGTERM received - shutting down ...\n"); @@ -2569,7 +2544,7 @@ check_nonce (ctrl_t ctrl, assuan_sock_nonce_t *nonce) if (assuan_sock_check_nonce (ctrl->thread_startup.fd, nonce)) { log_info (_("error reading nonce on fd %d: %s\n"), - FD2INT(ctrl->thread_startup.fd), strerror (errno)); + FD_DBG (ctrl->thread_startup.fd), strerror (errno)); assuan_sock_close (ctrl->thread_startup.fd); xfree (ctrl); return -1; @@ -2869,12 +2844,12 @@ do_start_connection_thread (ctrl_t ctrl) agent_init_default_ctrl (ctrl); if (opt.verbose > 1 && !DBG_IPC) log_info (_("handler 0x%lx for fd %d started\n"), - (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); + (unsigned long) npth_self(), FD_DBG (ctrl->thread_startup.fd)); start_command_handler (ctrl, GNUPG_INVALID_FD, ctrl->thread_startup.fd); if (opt.verbose > 1 && !DBG_IPC) log_info (_("handler 0x%lx for fd %d terminated\n"), - (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); + (unsigned long) npth_self(), FD_DBG (ctrl->thread_startup.fd)); agent_deinit_default_ctrl (ctrl); xfree (ctrl); @@ -2949,12 +2924,12 @@ start_connection_thread_ssh (void *arg) agent_init_default_ctrl (ctrl); if (opt.verbose) log_info (_("ssh handler 0x%lx for fd %d started\n"), - (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); + (unsigned long) npth_self(), FD_DBG (ctrl->thread_startup.fd)); start_command_handler_ssh (ctrl, ctrl->thread_startup.fd); if (opt.verbose) log_info (_("ssh handler 0x%lx for fd %d terminated\n"), - (unsigned long) npth_self(), FD2INT(ctrl->thread_startup.fd)); + (unsigned long) npth_self(), FD_DBG (ctrl->thread_startup.fd)); agent_deinit_default_ctrl (ctrl); xfree (ctrl); @@ -2963,6 +2938,28 @@ start_connection_thread_ssh (void *arg) } +void +agent_kick_the_loop (void) +{ + /* Kick the select loop. */ +#ifdef HAVE_W32_SYSTEM + int ret = SetEvent (the_event2); + if (ret == 0) + log_error ("SetEvent for agent_kick_the_loop failed: %s\n", + w32_strerror (-1)); +#else +# ifdef HAVE_PSELECT_NO_EINTR + write (event_pipe_fd, "", 1); +# else + int ret = kill (main_thread_pid, SIGCONT); + if (ret < 0) + log_error ("sending signal for agent_kick_the_loop failed: %s\n", + gpg_strerror (gpg_error_from_syserror ())); +# endif +#endif +} + + /* Connection handler loop. Wait for connection requests and spawn a thread after accepting a connection. */ static void @@ -2980,12 +2977,15 @@ handle_connections (gnupg_fd_t listen_fd, gnupg_fd_t fd; int nfd; int saved_errno; - struct timespec abstime; - struct timespec curtime; - struct timespec timeout; + struct timespec *tp; #ifdef HAVE_W32_SYSTEM - HANDLE events[2]; + HANDLE events[3]; unsigned int events_set; +#else + int signo; +# ifdef HAVE_PSELECT_NO_EINTR + int pipe_fd[2]; +# endif #endif int sock_inotify_fd = -1; int home_inotify_fd = -1; @@ -3013,11 +3013,24 @@ handle_connections (gnupg_fd_t listen_fd, npth_sigev_add (SIGUSR1); npth_sigev_add (SIGUSR2); npth_sigev_add (SIGINT); + npth_sigev_add (SIGCONT); npth_sigev_add (SIGTERM); npth_sigev_fini (); +# ifdef HAVE_PSELECT_NO_EINTR + ret = gnupg_create_pipe (pipe_fd); + if (ret) + { + log_error ("pipe creation failed: %s\n", gpg_strerror (ret)); + return; + } + event_pipe_fd = pipe_fd[1]; +# else + main_thread_pid = getpid (); +# endif #else events[0] = get_agent_daemon_notify_event (); - events[1] = INVALID_HANDLE_VALUE; + events[1] = the_event2 = create_an_event (); + events[2] = INVALID_HANDLE_VALUE; #endif if (disable_check_own_socket) @@ -3041,6 +3054,27 @@ handle_connections (gnupg_fd_t listen_fd, else have_homedir_inotify = 1; +#if CHECK_OWN_SOCKET_INTERVAL > 0 + if (!disable_check_own_socket && sock_inotify_fd == -1) + { + npth_t thread; + + err = npth_create (&thread, &tattr, check_own_socket_thread, NULL); + if (err) + log_error ("error spawning check_own_socket_thread: %s\n", strerror (err)); + } +#endif + + if ((HAVE_PARENT_PID_SUPPORT && parent_pid != (pid_t)(-1)) + || (!have_homedir_inotify || !reliable_homedir_inotify)) + { + npth_t thread; + + err = npth_create (&thread, &tattr, check_others_thread, NULL); + if (err) + log_error ("error spawning check_others_thread: %s\n", strerror (err)); + } + /* On Windows we need to fire up a separate thread to listen for requests from Putty (an SSH client), so we can replace Putty's Pageant (its ssh-agent implementation). */ @@ -3070,24 +3104,24 @@ handle_connections (gnupg_fd_t listen_fd, FD_ZERO (&fdset); FD_SET (FD2INT (listen_fd), &fdset); - nfd = FD2INT (listen_fd); + nfd = FD2NUM (listen_fd); if (listen_fd_extra != GNUPG_INVALID_FD) { FD_SET ( FD2INT(listen_fd_extra), &fdset); if (FD2INT (listen_fd_extra) > nfd) - nfd = FD2INT (listen_fd_extra); + nfd = FD2NUM (listen_fd_extra); } if (listen_fd_browser != GNUPG_INVALID_FD) { FD_SET ( FD2INT(listen_fd_browser), &fdset); if (FD2INT (listen_fd_browser) > nfd) - nfd = FD2INT (listen_fd_browser); + nfd = FD2NUM (listen_fd_browser); } if (listen_fd_ssh != GNUPG_INVALID_FD) { FD_SET ( FD2INT(listen_fd_ssh), &fdset); if (FD2INT (listen_fd_ssh) > nfd) - nfd = FD2INT (listen_fd_ssh); + nfd = FD2NUM (listen_fd_ssh); } if (sock_inotify_fd != -1) { @@ -3107,15 +3141,12 @@ handle_connections (gnupg_fd_t listen_fd, listentbl[2].l_fd = listen_fd_browser; listentbl[3].l_fd = listen_fd_ssh; - npth_clock_gettime (&abstime); - abstime.tv_sec += TIMERTICK_INTERVAL; - for (;;) { /* Shutdown test. */ if (shutdown_pending) { - if (active_connections == 0) + if (active_connections == 0 || is_supervised) break; /* ready */ /* Do not accept new connections but keep on running the @@ -3144,28 +3175,23 @@ handle_connections (gnupg_fd_t listen_fd, thus a simple assignment is fine to copy the entire set. */ read_fdset = fdset; - npth_clock_gettime (&curtime); - if (!(npth_timercmp (&curtime, &abstime, <))) - { - /* Timeout. */ - handle_tick (); - npth_clock_gettime (&abstime); - abstime.tv_sec += TIMERTICK_INTERVAL; - } - npth_timersub (&abstime, &curtime, &timeout); +#ifdef HAVE_PSELECT_NO_EINTR + FD_SET (pipe_fd[0], &read_fdset); + if (nfd < pipe_fd[0]) + nfd = pipe_fd[0]; +#endif + + tp = agent_cache_expiration (); #ifndef HAVE_W32_SYSTEM - ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout, + ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, tp, npth_sigev_sigmask ()); saved_errno = errno; - { - int signo; - while (npth_sigev_get_pending (&signo)) - handle_signal (signo); - } + while (npth_sigev_get_pending (&signo)) + handle_signal (signo); #else - ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout, + ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, tp, events, &events_set); saved_errno = errno; @@ -3181,11 +3207,47 @@ handle_connections (gnupg_fd_t listen_fd, gnupg_sleep (1); continue; } + +#ifndef HAVE_W32_SYSTEM + if ((problem_detected & AGENT_PROBLEM_PARENT_HAS_GONE)) + { + shutdown_pending = 2; + log_info ("parent process died - shutting down\n"); + log_info ("%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13)); + cleanup (); + agent_exit (0); + } +#endif + + if ((problem_detected & AGENT_PROBLEM_SOCKET_TAKEOVER)) + { + /* We may not remove the socket as it is now in use by another + server. */ + inhibit_socket_removal = 1; + shutdown_pending = 2; + log_info ("this process is useless - shutting down\n"); + } + + if ((problem_detected & AGENT_PROBLEM_HOMEDIR_REMOVED)) + { + shutdown_pending = 1; + log_info ("homedir has been removed - shutting down\n"); + } + if (ret <= 0) /* Interrupt or timeout. Will be handled when calculating the next timeout. */ continue; +#ifdef HAVE_PSELECT_NO_EINTR + if (FD_ISSET (pipe_fd[0], &read_fdset)) + { + char buf[256]; + + read (pipe_fd[0], buf, sizeof buf); + } +#endif + /* The inotify fds are set even when a shutdown is pending (see * above). So we must handle them in any case. To avoid that * they trigger a second time we close them immediately. */ @@ -3193,7 +3255,10 @@ handle_connections (gnupg_fd_t listen_fd, && FD_ISSET (sock_inotify_fd, &read_fdset) && gnupg_inotify_has_name (sock_inotify_fd, GPG_AGENT_SOCK_NAME)) { - shutdown_pending = 1; + /* We may not remove the socket (if any), as it may be now + in use by another server. */ + inhibit_socket_removal = 1; + shutdown_pending = 2; close (sock_inotify_fd); sock_inotify_fd = -1; log_info ("socket file has been removed - shutting down\n"); @@ -3222,8 +3287,8 @@ handle_connections (gnupg_fd_t listen_fd, continue; plen = sizeof paddr; - fd = INT2FD (npth_accept (FD2INT(listentbl[idx].l_fd), - (struct sockaddr *)&paddr, &plen)); + fd = assuan_sock_accept (listentbl[idx].l_fd, + (struct sockaddr *)&paddr, &plen); if (fd == GNUPG_INVALID_FD) { log_error ("accept failed for %s: %s\n", @@ -3263,13 +3328,21 @@ handle_connections (gnupg_fd_t listen_fd, close (sock_inotify_fd); if (home_inotify_fd != -1) close (home_inotify_fd); +#ifdef HAVE_W32_SYSTEM + if (the_event2 != INVALID_HANDLE_VALUE) + CloseHandle (the_event2); +#endif +#ifdef HAVE_PSELECT_NO_EINTR + close (pipe_fd[0]); + close (pipe_fd[1]); +#endif cleanup (); log_info (_("%s %s stopped\n"), gpgrt_strusage(11), gpgrt_strusage(13)); npth_attr_destroy (&tattr); } - +#if CHECK_OWN_SOCKET_INTERVAL > 0 /* Helper for check_own_socket. */ static gpg_error_t check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length) @@ -3280,20 +3353,18 @@ check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length) } -/* The thread running the actual check. We need to run this in a - separate thread so that check_own_thread can be called from the - timer tick. */ -static void * -check_own_socket_thread (void *arg) +/* Check whether we are still listening on our own socket. In case + another gpg-agent process started after us has taken ownership of + our socket, we would linger around without any real task. Thus we + better check once in a while whether we are really needed. */ +static int +do_check_own_socket (const char *sockname) { int rc; - char *sockname = arg; assuan_context_t ctx = NULL; membuf_t mb; char *buffer; - check_own_socket_running++; - rc = assuan_new (&ctx); if (rc) { @@ -3331,58 +3402,78 @@ check_own_socket_thread (void *arg) xfree (buffer); leave: - xfree (sockname); if (ctx) assuan_release (ctx); - if (rc) - { - /* We may not remove the socket as it is now in use by another - server. */ - inhibit_socket_removal = 1; - shutdown_pending = 2; - log_info ("this process is useless - shutting down\n"); - } - check_own_socket_running--; - return NULL; -} + return rc; +} -/* Check whether we are still listening on our own socket. In case - another gpg-agent process started after us has taken ownership of - our socket, we would linger around without any real task. Thus we - better check once in a while whether we are really needed. */ -static void -check_own_socket (void) +/* The thread running the actual check. */ +static void * +check_own_socket_thread (void *arg) { char *sockname; - npth_t thread; - npth_attr_t tattr; - int err; - - if (disable_check_own_socket) - return; - if (check_own_socket_running || shutdown_pending) - return; /* Still running or already shutting down. */ + (void)arg; sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL); if (!sockname) - return; /* Out of memory. */ + return NULL; /* Out of memory. */ - err = npth_attr_init (&tattr); - if (err) + while (!problem_detected) { - xfree (sockname); - return; + if (shutdown_pending) + goto leave; + + gnupg_sleep (CHECK_OWN_SOCKET_INTERVAL); + + if (do_check_own_socket (sockname)) + problem_detected |= AGENT_PROBLEM_SOCKET_TAKEOVER; } - npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); - err = npth_create (&thread, &tattr, check_own_socket_thread, sockname); - if (err) - log_error ("error spawning check_own_socket_thread: %s\n", strerror (err)); - npth_attr_destroy (&tattr); + + agent_kick_the_loop (); + + leave: + xfree (sockname); + return NULL; } +#endif + +/* The thread running other checks. */ +static void * +check_others_thread (void *arg) +{ + const char *homedir = gnupg_homedir (); + + (void)arg; + while (!problem_detected) + { + struct stat statbuf; + if (shutdown_pending) + goto leave; + + gnupg_sleep (CHECK_PROBLEMS_INTERVAL); + + /* If we are running as a child of another process, check whether + the parent is still alive and shutdown if not. */ +#ifndef HAVE_W32_SYSTEM + if (parent_pid != (pid_t)(-1) && kill (parent_pid, 0)) + problem_detected |= AGENT_PROBLEM_PARENT_HAS_GONE; +#endif /*HAVE_W32_SYSTEM*/ + + /* Check whether the homedir is still available. */ + if ((!have_homedir_inotify || !reliable_homedir_inotify) + && gnupg_stat (homedir, &statbuf) && errno == ENOENT) + problem_detected |= AGENT_PROBLEM_HOMEDIR_REMOVED; + } + + agent_kick_the_loop (); + + leave: + return NULL; +} /* Figure out whether an agent is available and running. Prints an error if not. If SILENT is true, no messages are printed. |