diff options
Diffstat (limited to 'agent')
-rw-r--r-- | agent/agent.h | 3 | ||||
-rw-r--r-- | agent/cache.c | 358 | ||||
-rw-r--r-- | agent/call-daemon.c | 51 | ||||
-rw-r--r-- | agent/call-pinentry.c | 44 | ||||
-rw-r--r-- | agent/command-ssh.c | 4 | ||||
-rw-r--r-- | agent/genkey.c | 18 | ||||
-rw-r--r-- | agent/gpg-agent.c | 459 | ||||
-rw-r--r-- | agent/trustlist.c | 14 |
8 files changed, 603 insertions, 348 deletions
diff --git a/agent/agent.h b/agent/agent.h index 531fad210..3bedab121 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -411,6 +411,7 @@ void *get_agent_daemon_notify_event (void); #endif void agent_sighup_action (void); int map_pk_openpgp_to_gcry (int openpgp_algo); +void agent_kick_the_loop (void); /*-- command.c --*/ gpg_error_t agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid, @@ -514,7 +515,7 @@ int agent_clear_passphrase (ctrl_t ctrl, /*-- cache.c --*/ void initialize_module_cache (void); void deinitialize_module_cache (void); -void agent_cache_housekeeping (void); +struct timespec *agent_cache_expiration (void); void agent_flush_cache (int pincache_only); int agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode, const char *data, int ttl); diff --git a/agent/cache.c b/agent/cache.c index 7616dafc1..e8544205f 100644 --- a/agent/cache.c +++ b/agent/cache.c @@ -53,8 +53,20 @@ struct secret_data_s { char data[1]; /* A string. */ }; -/* The cache object. */ +/* The type of cache object. */ typedef struct cache_item_s *ITEM; + +/* The timer entry in a linked list. */ +struct timer_s { + ITEM next; + int tv_sec; + int reason; +}; +#define CACHE_EXPIRE_UNUSED 0 +#define CACHE_EXPIRE_LAST_ACCESS 1 +#define CACHE_EXPIRE_CREATION 2 + +/* The cache object. */ struct cache_item_s { ITEM next; time_t created; @@ -63,12 +75,18 @@ struct cache_item_s { struct secret_data_s *pw; cache_mode_t cache_mode; int restricted; /* The value of ctrl->restricted is part of the key. */ + struct timer_s t; char key[1]; }; /* The cache himself. */ static ITEM thecache; +/* The timer list of expiration, in active. */ +static ITEM the_timer_list; +/* Newly created entries, to be inserted into the timer list. */ +static ITEM the_timer_list_new; + /* NULL or the last cache key stored by agent_store_cache_hit. */ static char *last_stored_cache_key; @@ -193,100 +211,302 @@ new_data (const char *string, struct secret_data_s **r_data) } +static void +insert_to_timer_list_new (ITEM entry) +{ + entry->t.next = the_timer_list_new; + the_timer_list_new = entry; +} -/* Check whether there are items to expire. */ +/* Insert to the active timer list. */ static void -housekeeping (void) +insert_to_timer_list (struct timespec *ts, ITEM entry) { - ITEM r, rprev; - time_t current = gnupg_get_time (); + ITEM e, eprev; - /* First expire the actual data */ - for (r=thecache; r; r = r->next) + if (!the_timer_list || ts->tv_sec >= entry->t.tv_sec) { - if (r->cache_mode == CACHE_MODE_PIN) - ; /* Don't let it expire - scdaemon explicitly flushes them. */ - else if (r->pw && r->ttl >= 0 && r->accessed + r->ttl < current) + if (the_timer_list) { - if (DBG_CACHE) - log_debug (" expired '%s'.%d (%ds after last access)\n", - r->key, r->restricted, r->ttl); - release_data (r->pw); - r->pw = NULL; - r->accessed = current; + the_timer_list->t.tv_sec += ts->tv_sec - entry->t.tv_sec; + if (ts->tv_nsec >= 500000000) + the_timer_list->t.tv_sec++; } + + ts->tv_sec = entry->t.tv_sec; + ts->tv_nsec = 0; + + entry->t.tv_sec = 0; + entry->t.next = the_timer_list; + the_timer_list = entry; + return; } - /* Second, make sure that we also remove them based on the created - * stamp so that the user has to enter it from time to time. We - * don't do this for data items which are used to storage secrets in - * meory and are not user entered passphrases etc. */ - for (r=thecache; r; r = r->next) + entry->t.tv_sec -= ts->tv_sec; + eprev = NULL; + for (e = the_timer_list; e; e = e->t.next) { - unsigned long maxttl; + if (e->t.tv_sec > entry->t.tv_sec) + break; - switch (r->cache_mode) - { - case CACHE_MODE_DATA: - case CACHE_MODE_PIN: - continue; /* No MAX TTL here. */ - case CACHE_MODE_SSH: maxttl = opt.max_cache_ttl_ssh; break; - default: maxttl = opt.max_cache_ttl; break; - } - if (r->pw && r->created + maxttl < current) - { - if (DBG_CACHE) - log_debug (" expired '%s'.%d (%lus after creation)\n", - r->key, r->restricted, opt.max_cache_ttl); - release_data (r->pw); - r->pw = NULL; - r->accessed = current; - } + eprev = e; + entry->t.tv_sec -= e->t.tv_sec; + } + + entry->t.next = e; + if (e) + e->t.tv_sec -= entry->t.tv_sec; + + if (eprev) + eprev->t.next = entry; + else + the_timer_list = entry; +} + +static void +remove_from_timer_list (ITEM entry) +{ + ITEM e, eprev; + + eprev = NULL; + for (e = the_timer_list; e; e = e->t.next) + if (e != entry) + eprev = e; + else + { + if (e->t.next) + e->t.next->t.tv_sec += e->t.tv_sec; + + if (eprev) + eprev->t.next = e->t.next; + else + the_timer_list = e->t.next; + + break; + } + + entry->t.next = NULL; + entry->t.tv_sec = 0; +} + +static void +remove_from_timer_list_new (ITEM entry) +{ + ITEM e, eprev; + + eprev = NULL; + for (e = the_timer_list_new; e; e = e->t.next) + if (e != entry) + eprev = e; + else + { + if (eprev) + eprev->t.next = e->t.next; + else + the_timer_list_new = e->t.next; + + break; + } + + entry->t.next = NULL; + entry->t.tv_sec = 0; +} + +static int +compute_expiration (ITEM r) +{ + unsigned long maxttl; + time_t current = gnupg_get_time (); + time_t next; + + if (r->cache_mode == CACHE_MODE_PIN) + return 0; /* Don't let it expire - scdaemon explicitly flushes them. */ + + if (!r->pw) + { + /* Expire an old and unused entry after 30 minutes. */ + r->t.tv_sec = 60*30; + r->t.reason = CACHE_EXPIRE_UNUSED; + return 1; } - /* Third, make sure that we don't have too many items in the list. - * Expire old and unused entries after 30 minutes. */ - for (rprev=NULL, r=thecache; r; ) + switch (r->cache_mode) { - if (!r->pw && r->ttl >= 0 && r->accessed + 60*30 < current) - { - ITEM r2 = r->next; - if (DBG_CACHE) - log_debug (" removed '%s'.%d (mode %d) (slot not used for 30m)\n", - r->key, r->restricted, r->cache_mode); - xfree (r); - if (!rprev) - thecache = r2; - else - rprev->next = r2; - r = r2; - } - else + case CACHE_MODE_DATA: + case CACHE_MODE_PIN: + maxttl = 0; /* No MAX TTL here. */ + break; + case CACHE_MODE_SSH: maxttl = opt.max_cache_ttl_ssh; break; + default: maxttl = opt.max_cache_ttl; break; + } + + if (maxttl) + { + if (r->created + maxttl < current) { - rprev = r; - r = r->next; + r->t.tv_sec = 0; + r->t.reason = CACHE_EXPIRE_CREATION; + return 1; } + + next = r->created + maxttl - current; + } + else + next = 0; + + if (r->ttl >= 0 && (next == 0 || r->ttl < next)) + { + r->t.tv_sec = r->ttl; + r->t.reason = CACHE_EXPIRE_LAST_ACCESS; + return 1; } + + if (next) + { + r->t.tv_sec = next; + r->t.reason = CACHE_EXPIRE_CREATION; + return 1; + } + + return 0; } +static void +update_expiration (ITEM entry, int is_new_entry) +{ + if (!is_new_entry) + { + remove_from_timer_list (entry); + remove_from_timer_list_new (entry); + } -void -agent_cache_housekeeping (void) + if (compute_expiration (entry)) + { + insert_to_timer_list_new (entry); + agent_kick_the_loop (); + } +} + + +/* Expire the cache entry. Returns 1 when the entry should be removed + * from the cache. */ +static int +do_expire (ITEM e) { - int res; + if (!e->pw) + /* Unused entry after 30 minutes. */ + return 1; - if (DBG_CACHE) - log_debug ("agent_cache_housekeeping\n"); + if (e->t.reason == CACHE_EXPIRE_LAST_ACCESS) + { + if (DBG_CACHE) + log_debug (" expired '%s'.%d (%ds after last access)\n", + e->key, e->restricted, e->ttl); + } + else + { + if (DBG_CACHE) + log_debug (" expired '%s'.%d (%lus after creation)\n", + e->key, e->restricted, opt.max_cache_ttl); + } + + release_data (e->pw); + e->pw = NULL; + e->accessed = 0; + + if (compute_expiration (e)) + insert_to_timer_list_new (e); + + return 0; +} + + +struct timespec * +agent_cache_expiration (void) +{ + static struct timespec abstime; + static struct timespec timeout; + struct timespec *tp; + struct timespec curtime; + int res; + int expired = 0; + ITEM e, enext; res = npth_mutex_lock (&cache_lock); if (res) log_fatal ("failed to acquire cache mutex: %s\n", strerror (res)); - housekeeping (); + npth_clock_gettime (&curtime); + if (the_timer_list) + { + if (npth_timercmp (&abstime, &curtime, <)) + expired = 1; + else + npth_timersub (&abstime, &curtime, &timeout); + } + + if (expired && (e = the_timer_list) && e->t.tv_sec == 0) + { + the_timer_list = e->t.next; + e->t.next = NULL; + + if (do_expire (e)) + { + ITEM r, rprev; + + if (DBG_CACHE) + log_debug (" removed '%s'.%d (mode %d) (slot not used for 30m)\n", + e->key, e->restricted, e->cache_mode); + + rprev = NULL; + for (r = thecache; r; r = r->next) + if (r == e) + { + if (!rprev) + thecache = r->next; + else + rprev->next = r->next; + break; + } + else + rprev = r; + + remove_from_timer_list_new (e); + + xfree (e); + } + } + + if (expired || !the_timer_list) + timeout.tv_sec = timeout.tv_nsec = 0; + + for (e = the_timer_list_new; e; e = enext) + { + enext = e->t.next; + e->t.next = NULL; + insert_to_timer_list (&timeout, e); + } + the_timer_list_new = NULL; + + if (!the_timer_list) + tp = NULL; + else + { + if (the_timer_list->t.tv_sec != 0) + { + timeout.tv_sec += the_timer_list->t.tv_sec; + the_timer_list->t.tv_sec = 0; + } + + npth_timeradd (&timeout, &curtime, &abstime); + tp = &timeout; + } res = npth_mutex_unlock (&cache_lock); if (res) log_fatal ("failed to release cache mutex: %s\n", strerror (res)); + + return tp; } @@ -314,6 +534,7 @@ agent_flush_cache (int pincache_only) release_data (r->pw); r->pw = NULL; r->accessed = 0; + update_expiration (r, 0); } } @@ -358,7 +579,6 @@ agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode, if (DBG_CACHE) log_debug ("agent_put_cache '%s'.%d (mode %d) requested ttl=%d\n", key, restricted, cache_mode, ttl); - housekeeping (); if (!ttl) { @@ -410,6 +630,7 @@ agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode, err = new_data (data, &r->pw); if (err) log_error ("error replacing cache item: %s\n", gpg_strerror (err)); + update_expiration (r, 0); } } else if (data) /* Insert. */ @@ -431,6 +652,7 @@ agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode, { r->next = thecache; thecache = r; + update_expiration (r, 1); } } if (err) @@ -478,7 +700,6 @@ agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode) log_debug ("agent_get_cache '%s'.%d (mode %d)%s ...\n", key, restricted, cache_mode, last_stored? " (stored cache key)":""); - housekeeping (); for (r=thecache; r; r = r->next) { @@ -500,7 +721,10 @@ agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode) * below. Note also that we don't update the accessed time * for data items. */ if (r->cache_mode != CACHE_MODE_DATA) - r->accessed = gnupg_get_time (); + { + r->accessed = gnupg_get_time (); + update_expiration (r, 0); + } if (DBG_CACHE) log_debug ("... hit\n"); if (r->pw->totallen < 32) diff --git a/agent/call-daemon.c b/agent/call-daemon.c index e1c5669e9..806ef5dc1 100644 --- a/agent/call-daemon.c +++ b/agent/call-daemon.c @@ -98,7 +98,6 @@ static npth_mutex_t start_daemon_lock; struct wait_child_thread_parm_s { enum daemon_type type; - pid_t pid; }; @@ -109,54 +108,14 @@ wait_child_thread (void *arg) int err; struct wait_child_thread_parm_s *parm = arg; enum daemon_type type = parm->type; - pid_t pid = parm->pid; -#ifndef HAVE_W32_SYSTEM - int wstatus; -#endif const char *name = opt.daemon_program[type]; struct daemon_global_s *g = &daemon_global[type]; struct daemon_local_s *sl; xfree (parm); /* We have copied all data to the stack. */ -#ifdef HAVE_W32_SYSTEM - npth_unprotect (); - /* Note that although we use a pid_t here, it is actually a HANDLE. */ - WaitForSingleObject ((HANDLE)pid, INFINITE); - npth_protect (); + assuan_pipe_wait_server_termination (g->primary_ctx, NULL, 0); log_info ("daemon %s finished\n", name); -#else /* !HAVE_W32_SYSTEM*/ - - again: - npth_unprotect (); - err = waitpid (pid, &wstatus, 0); - npth_protect (); - - if (err < 0) - { - if (errno == EINTR) - goto again; - log_error ("waitpid for %s failed: %s\n", name, strerror (errno)); - return NULL; - } - else - { - if (WIFEXITED (wstatus)) - log_info ("daemon %s finished (status %d)\n", - name, WEXITSTATUS (wstatus)); - else if (WIFSIGNALED (wstatus)) - log_info ("daemon %s killed by signal %d\n", name, WTERMSIG (wstatus)); - else - { - if (WIFSTOPPED (wstatus)) - log_info ("daemon %s stopped by signal %d\n", - name, WSTOPSIG (wstatus)); - goto again; - } - - assuan_set_flag (g->primary_ctx, ASSUAN_NO_WAITPID, 1); - } -#endif /*!HAVE_W32_SYSTEM*/ agent_flush_cache (1); /* Flush the PIN cache. */ @@ -471,8 +430,8 @@ daemon_start (enum daemon_type type, ctrl_t ctrl) char buf[100]; #ifdef HAVE_W32_SYSTEM - snprintf (buf, sizeof buf, "OPTION event-signal=%lx", - (unsigned long)get_agent_daemon_notify_event ()); + snprintf (buf, sizeof buf, "OPTION event-signal=%p", + get_agent_daemon_notify_event ()); #else snprintf (buf, sizeof buf, "OPTION event-signal=%d", SIGUSR2); #endif @@ -496,7 +455,6 @@ daemon_start (enum daemon_type type, ctrl_t ctrl) } wctp->type = type; - wctp->pid = assuan_get_pid (g->primary_ctx); err = npth_attr_init (&tattr); if (!err) { @@ -561,10 +519,9 @@ agent_daemon_dump_state (void) for (i = 0; i < DAEMON_MAX_TYPE; i++) { struct daemon_global_s *g = &daemon_global[i]; - log_info ("%s: name %s primary_ctx=%p pid=%ld reusable=%d\n", __func__, + log_info ("%s: name %s primary_ctx=%p reusable=%d\n", __func__, gnupg_module_name (daemon_modules[i]), g->primary_ctx, - (long)assuan_get_pid (g->primary_ctx), g->primary_ctx_reusable); if (g->socket_name) log_info ("%s: socket='%s'\n", __func__, g->socket_name); diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c index d236e1107..4a999ca9a 100644 --- a/agent/call-pinentry.c +++ b/agent/call-pinentry.c @@ -128,8 +128,9 @@ initialize_module_call_pinentry (void) void agent_query_dump_state (void) { - log_info ("agent_query_dump_state: entry_ctx=%p pid=%ld popup_tid=%p\n", - entry_ctx, (long)assuan_get_pid (entry_ctx), (void*)popup_tid); + log_info ("agent_query_dump_state: entry_ctx=%p pid=%ld popup_tid=%lx\n", + entry_ctx, (long)assuan_get_pid (entry_ctx), + (unsigned long)popup_tid); } /* Called to make sure that a popup window owned by the current @@ -1288,8 +1289,6 @@ build_cmd_setdesc (char *line, size_t linelen, const char *desc) static void * watch_sock (void *arg) { - pid_t pid = assuan_get_pid (entry_ctx); - while (1) { int err; @@ -1302,7 +1301,7 @@ watch_sock (void *arg) FD_ZERO (&fdset); FD_SET (FD2INT (sock), &fdset); - err = npth_select (FD2INT (sock)+1, &fdset, NULL, NULL, &timeout); + err = npth_select (FD2NUM (sock)+1, &fdset, NULL, NULL, &timeout); if (err < 0) { @@ -1317,17 +1316,7 @@ watch_sock (void *arg) break; } - if (pid == (pid_t)(-1)) - ; /* No pid available can't send a kill. */ -#ifdef HAVE_W32_SYSTEM - /* Older versions of assuan set PID to 0 on Windows to indicate an - invalid value. */ - else if (pid != (pid_t) INVALID_HANDLE_VALUE && pid != 0) - TerminateProcess ((HANDLE)pid, 1); -#else - else if (pid > 0) - kill (pid, SIGINT); -#endif + assuan_pipe_kill_server (entry_ctx); return NULL; } @@ -2124,7 +2113,6 @@ void agent_popup_message_stop (ctrl_t ctrl) { int rc; - pid_t pid; (void)ctrl; @@ -2137,26 +2125,10 @@ agent_popup_message_stop (ctrl_t ctrl) return; } - pid = assuan_get_pid (entry_ctx); - if (pid == (pid_t)(-1)) - ; /* No pid available can't send a kill. */ - else if (popup_finished) + if (popup_finished) ; /* Already finished and ready for joining. */ -#ifdef HAVE_W32_SYSTEM - /* Older versions of assuan set PID to 0 on Windows to indicate an - invalid value. */ - else if (pid != (pid_t) INVALID_HANDLE_VALUE - && pid != 0) - { - HANDLE process = (HANDLE) pid; - - /* Arbitrary error code. */ - TerminateProcess (process, 1); - } -#else - else if (pid > 0) - kill (pid, SIGINT); -#endif + else + assuan_pipe_kill_server (entry_ctx); /* Now wait for the thread to terminate. */ rc = npth_join (popup_tid, NULL); diff --git a/agent/command-ssh.c b/agent/command-ssh.c index ca3993321..0afa24111 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -3952,7 +3952,11 @@ start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client) es_syshd_t syshd; syshd.type = ES_SYSHD_SOCK; +#ifdef HAVE_SOCKET + syshd.u.sock = (SOCKET)sock_client; +#else syshd.u.sock = sock_client; +#endif get_client_info (sock_client, &peer_info); ctrl->client_pid = peer_info.pid; diff --git a/agent/genkey.c b/agent/genkey.c index 741c05f4f..cf37cdafc 100644 --- a/agent/genkey.c +++ b/agent/genkey.c @@ -100,7 +100,7 @@ do_check_passphrase_pattern (ctrl_t ctrl, const char *pw, unsigned int flags) const char *pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CHECK_PATTERN); estream_t stream_to_check_pattern = NULL; const char *argv[10]; - pid_t pid; + gnupg_process_t proc; int result, i; const char *pattern; char *patternfname; @@ -143,11 +143,17 @@ do_check_passphrase_pattern (ctrl_t ctrl, const char *pw, unsigned int flags) argv[i] = NULL; log_assert (i < sizeof argv); - if (gnupg_spawn_process (pgmname, argv, NULL, 0, - &stream_to_check_pattern, NULL, NULL, &pid)) + if (gnupg_process_spawn (pgmname, argv, + GNUPG_PROCESS_STDIN_PIPE, + NULL, NULL, &proc)) result = 1; /* Execute error - assume password should no be used. */ else { + int status; + + gnupg_process_get_streams (proc, 0, &stream_to_check_pattern, + NULL, NULL); + es_set_binary (stream_to_check_pattern); if (es_fwrite (pw, strlen (pw), 1, stream_to_check_pattern) != 1) { @@ -158,11 +164,13 @@ do_check_passphrase_pattern (ctrl_t ctrl, const char *pw, unsigned int flags) else es_fflush (stream_to_check_pattern); es_fclose (stream_to_check_pattern); - if (gnupg_wait_process (pgmname, pid, 1, NULL)) + gnupg_process_wait (proc, 1); + gnupg_process_ctl (proc, GNUPG_PROCESS_GET_EXIT_ID, &status); + if (status) result = 1; /* Helper returned an error - probably a match. */ else result = 0; /* Success; i.e. no match. */ - gnupg_release_process (pid); + gnupg_process_release (proc); } xfree (patternfname); diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 1db422737..185957a0f 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_INTERVAL 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) @@ -3029,10 +3042,8 @@ handle_connections (gnupg_fd_t listen_fd, gpg_strerror (err)); } - if (disable_check_own_socket) - home_inotify_fd = -1; - else if ((err = gnupg_inotify_watch_delete_self (&home_inotify_fd, - gnupg_homedir ()))) + if ((err = gnupg_inotify_watch_delete_self (&home_inotify_fd, + gnupg_homedir ()))) { if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED) log_info ("error enabling daemon termination by homedir removal: %s\n", @@ -3041,6 +3052,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 +3102,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 +3139,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 +3173,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 +3205,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 +3253,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 +3285,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 +3326,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 +3351,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 +3400,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. diff --git a/agent/trustlist.c b/agent/trustlist.c index 330f233b8..fce23de15 100644 --- a/agent/trustlist.c +++ b/agent/trustlist.c @@ -38,14 +38,14 @@ struct trustitem_s { struct { - int disabled:1; /* This entry is disabled. */ - int for_pgp:1; /* Set by '*' or 'P' as first flag. */ - int for_smime:1; /* Set by '*' or 'S' as first flag. */ - int relax:1; /* Relax checking of root certificate + unsigned int disabled:1; /* This entry is disabled. */ + unsigned int for_pgp:1; /* Set by '*' or 'P' as first flag. */ + unsigned int for_smime:1; /* Set by '*' or 'S' as first flag. */ + unsigned int relax:1; /* Relax checking of root certificate constraints. */ - int cm:1; /* Use chain model for validation. */ - int qual:1; /* Root CA for qualified signatures. */ - int de_vs:1; /* Root CA for de-vs compliant PKI. */ + unsigned int cm:1; /* Use chain model for validation. */ + unsigned int qual:1; /* Root CA for qualified signatures. */ + unsigned int de_vs:1; /* Root CA for de-vs compliant PKI. */ } flags; unsigned char fpr[20]; /* The binary fingerprint. */ }; |