kbx: Have threads monitoring socket takeover and homedir if no inotify.

* kbx/keyboxd.c (CHECK_PROBLEMS_INTERVAL): New.
(have_homedir_inotify): Remove the global.
[HAVE_W32_SYSTEM] (create_an_event): New.
(handle_tick): Remove.
(handle_signal): Add handling SIGCONT.
(keyboxd_kick_the_loop): New.
(handle_connections): Spawn check_own_socket_thread and
check_others_thread if no inotify.
(check_own_socket_thread, check_others_thread): New.

--

This change follows the change of gpg-agent.

Signed-off-by: NIIBE Yutaka <gniibe@fsij.org>
This commit is contained in:
NIIBE Yutaka 2024-01-24 13:45:49 +09:00
parent 6ddaf2be9f
commit ccfbb9ebdf
No known key found for this signature in database
GPG Key ID: 640114AF89DE6054
1 changed files with 231 additions and 119 deletions

View File

@ -144,14 +144,13 @@ static struct debug_flags_s debug_flags [] =
{ 77, NULL } /* 77 := Do not exit on "help" or "?". */ { 77, NULL } /* 77 := Do not exit on "help" or "?". */
}; };
/* The timer tick used for housekeeping stuff. Note that on Windows /* CHECK_OWN_SOCKET_INTERVAL defines how often we check our own socket
* we use a SetWaitableTimer seems to signal earlier than about 2 * in standard socket mode. If that value is 0 we don't check at all.
* seconds. Thus we use 4 seconds on all platforms. * Values is in seconds. */
* CHECK_OWN_SOCKET_INTERVAL defines how often we check #define CHECK_OWN_SOCKET_INTERVAL (60)
* our own socket in standard socket mode. If that value is 0 we /* CHECK_PROBLEMS_INTERVAL defines how often we check the existence of
* don't check at all. All values are in seconds. */ * homedir. Value is in seconds. */
# define TIMERTICK_INTERVAL (4) #define CHECK_PROBLEMS_INTERVAL (4)
# define CHECK_OWN_SOCKET_INTERVAL (60)
/* The list of open file descriptors at startup. Note that this list /* The list of open file descriptors at startup. Note that this list
* has been allocated using the standard malloc. */ * has been allocated using the standard malloc. */
@ -171,8 +170,10 @@ static int shutdown_pending;
/* Flag indicating to start the daemon even if one already runs. */ /* Flag indicating to start the daemon even if one already runs. */
static int steal_socket; static int steal_socket;
/* Counter for the currently running own socket checks. */ /* Flag to monitor problems. */
static int check_own_socket_running; static int problem_detected;
#define KEYBOXD_PROBLEM_SOCKET_TAKEOVER (1 << 0)
#define KEYBOXD_PROBLEM_HOMEDIR_REMOVED (1 << 1)
/* Flag to indicate that we shall not watch our own socket. */ /* Flag to indicate that we shall not watch our own socket. */
static int disable_check_own_socket; static int disable_check_own_socket;
@ -192,6 +193,17 @@ static assuan_sock_nonce_t socket_nonce;
* Let's try this as default. Change at runtime with --listen-backlog. */ * Let's try this as default. Change at runtime with --listen-backlog. */
static int listen_backlog = 64; 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
/* Name of a config file, which will be reread on a HUP if it is not NULL. */ /* Name of a config file, which will be reread on a HUP if it is not NULL. */
static char *config_filename; static char *config_filename;
@ -199,11 +211,6 @@ static char *config_filename;
* the log file after a SIGHUP if it didn't changed. Malloced. */ * the log file after a SIGHUP if it didn't changed. Malloced. */
static char *current_logfile; static char *current_logfile;
/* This flag is true if the inotify mechanism for detecting the
* removal of the homedir is active. This flag is used to disable the
* alternative but portable stat based check. */
static int have_homedir_inotify;
/* Number of active connections. */ /* Number of active connections. */
static int active_connections; static int active_connections;
@ -250,8 +257,11 @@ static void kbxd_init_default_ctrl (ctrl_t ctrl);
static void kbxd_deinit_default_ctrl (ctrl_t ctrl); static void kbxd_deinit_default_ctrl (ctrl_t ctrl);
static void handle_connections (gnupg_fd_t listen_fd); static void handle_connections (gnupg_fd_t listen_fd);
static void check_own_socket (void);
static int check_for_running_kbxd (int silent); static int check_for_running_kbxd (int silent);
#if CHECK_OWN_SOCKET_INTERVAL > 0
static void *check_own_socket_thread (void *arg);
#endif
static void *check_others_thread (void *arg);
/* /*
* Functions. * Functions.
@ -1055,6 +1065,44 @@ get_kbxd_active_connection_count (void)
} }
/* Under W32, this function returns the handle of the scdaemon
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;
}
#endif /*HAVE_W32_SYSTEM*/
/* Create a name for the socket in the home directory as using /* Create a name for the socket in the home directory as using
* STANDARD_NAME. We also check for valid characters as well as * STANDARD_NAME. We also check for valid characters as well as
* against a maximum allowed length for a Unix domain socket is done. * against a maximum allowed length for a Unix domain socket is done.
@ -1267,38 +1315,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);
/* 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
/* Check whether the homedir is still available. */
if (!shutdown_pending
&& !have_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 /* A global function which allows us to call the reload stuff from
* other places too. This is only used when build for W32. */ * other places too. This is only used when build for W32. */
void void
@ -1344,6 +1360,11 @@ handle_signal (int signo)
kbxd_sigusr2_action (); kbxd_sigusr2_action ();
break; break;
case SIGCONT:
/* Do nothing, but break the syscall. */
log_debug ("SIGCONT received - breaking select\n");
break;
case SIGTERM: case SIGTERM:
if (!shutdown_pending) if (!shutdown_pending)
log_info ("SIGTERM received - shutting down ...\n"); log_info ("SIGTERM received - shutting down ...\n");
@ -1435,6 +1456,28 @@ start_connection_thread (void *arg)
} }
static void
keyboxd_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 /* Connection handler loop. Wait for connection requests and spawn a
* thread after accepting a connection. */ * thread after accepting a connection. */
static void static void
@ -1449,12 +1492,14 @@ handle_connections (gnupg_fd_t listen_fd)
gnupg_fd_t fd; gnupg_fd_t fd;
int nfd; int nfd;
int saved_errno; int saved_errno;
struct timespec abstime;
struct timespec curtime;
struct timespec timeout;
#ifdef HAVE_W32_SYSTEM #ifdef HAVE_W32_SYSTEM
HANDLE events[2]; HANDLE events[2];
unsigned int events_set; unsigned int events_set;
#else
int signo;
# ifdef HAVE_PSELECT_NO_EINTR
int pipe_fd[2];
# endif
#endif #endif
int sock_inotify_fd = -1; int sock_inotify_fd = -1;
int home_inotify_fd = -1; int home_inotify_fd = -1;
@ -1465,7 +1510,7 @@ handle_connections (gnupg_fd_t listen_fd)
} listentbl[] = { } listentbl[] = {
{ "std", start_connection_thread }, { "std", start_connection_thread },
}; };
int have_homedir_inotify = 0;
ret = npth_attr_init(&tattr); ret = npth_attr_init(&tattr);
if (ret) if (ret)
@ -1478,10 +1523,23 @@ handle_connections (gnupg_fd_t listen_fd)
npth_sigev_add (SIGUSR1); npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2); npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT); npth_sigev_add (SIGINT);
npth_sigev_add (SIGCONT);
npth_sigev_add (SIGTERM); npth_sigev_add (SIGTERM);
npth_sigev_fini (); 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 #else
events[0] = INVALID_HANDLE_VALUE; events[0] = the_event2 = create_an_event ();
events[1] = INVALID_HANDLE_VALUE;
#endif #endif
if (disable_check_own_socket) if (disable_check_own_socket)
@ -1503,6 +1561,26 @@ handle_connections (gnupg_fd_t listen_fd)
else else
have_homedir_inotify = 1; 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_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));
}
FD_ZERO (&fdset); FD_ZERO (&fdset);
FD_SET (FD2INT (listen_fd), &fdset); FD_SET (FD2INT (listen_fd), &fdset);
nfd = FD2NUM (listen_fd); nfd = FD2NUM (listen_fd);
@ -1521,9 +1599,6 @@ handle_connections (gnupg_fd_t listen_fd)
listentbl[0].l_fd = listen_fd; listentbl[0].l_fd = listen_fd;
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
for (;;) for (;;)
{ {
/* Shutdown test. */ /* Shutdown test. */
@ -1556,28 +1631,21 @@ handle_connections (gnupg_fd_t listen_fd)
read_fdset = fdset; read_fdset = fdset;
npth_clock_gettime (&curtime); #ifdef HAVE_PSELECT_NO_EINTR
if (!(npth_timercmp (&curtime, &abstime, <))) FD_SET (pipe_fd[0], &read_fdset);
{ if (nfd < pipe_fd[0])
/* Timeout. */ nfd = pipe_fd[0];
handle_tick (); #endif
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
}
npth_timersub (&abstime, &curtime, &timeout);
#ifndef HAVE_W32_SYSTEM #ifndef HAVE_W32_SYSTEM
ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout, ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, NULL,
npth_sigev_sigmask ()); npth_sigev_sigmask ());
saved_errno = errno; saved_errno = errno;
{ while (npth_sigev_get_pending (&signo))
int signo; handle_signal (signo);
while (npth_sigev_get_pending (&signo))
handle_signal (signo);
}
#else #else
ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout, ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, NULL,
events, &events_set); events, &events_set);
saved_errno = errno; saved_errno = errno;
@ -1593,6 +1661,22 @@ handle_connections (gnupg_fd_t listen_fd)
gnupg_sleep (1); gnupg_sleep (1);
continue; continue;
} }
if ((problem_detected & KEYBOXD_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 & KEYBOXD_PROBLEM_HOMEDIR_REMOVED))
{
shutdown_pending = 1;
log_info ("homedir has been removed - shutting down\n");
}
if (ret <= 0) if (ret <= 0)
{ {
/* Interrupt or timeout. Will be handled when calculating the /* Interrupt or timeout. Will be handled when calculating the
@ -1600,6 +1684,15 @@ handle_connections (gnupg_fd_t listen_fd)
continue; 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 /* The inotify fds are set even when a shutdown is pending (see
* above). So we must handle them in any case. To avoid that * above). So we must handle them in any case. To avoid that
* they trigger a second time we close them immediately. */ * they trigger a second time we close them immediately. */
@ -1670,13 +1763,21 @@ handle_connections (gnupg_fd_t listen_fd)
close (sock_inotify_fd); close (sock_inotify_fd);
if (home_inotify_fd != -1) if (home_inotify_fd != -1)
close (home_inotify_fd); 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 (); cleanup ();
log_info (_("%s %s stopped\n"), gpgrt_strusage(11), gpgrt_strusage(13)); log_info (_("%s %s stopped\n"), gpgrt_strusage(11), gpgrt_strusage(13));
npth_attr_destroy (&tattr); npth_attr_destroy (&tattr);
} }
#if CHECK_OWN_SOCKET_INTERVAL > 0
/* Helper for check_own_socket. */ /* Helper for check_own_socket. */
static gpg_error_t static gpg_error_t
check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length) check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length)
@ -1687,20 +1788,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 /* Check whether we are still listening on our own socket. In case
* separate thread so that check_own_thread can be called from the another gpg-agent process started after us has taken ownership of
* timer tick. */ our socket, we would linger around without any real task. Thus we
static void * better check once in a while whether we are really needed. */
check_own_socket_thread (void *arg) static int
do_check_own_socket (const char *sockname)
{ {
int rc; int rc;
char *sockname = arg;
assuan_context_t ctx = NULL; assuan_context_t ctx = NULL;
membuf_t mb; membuf_t mb;
char *buffer; char *buffer;
check_own_socket_running++;
rc = assuan_new (&ctx); rc = assuan_new (&ctx);
if (rc) if (rc)
{ {
@ -1738,57 +1837,70 @@ check_own_socket_thread (void *arg)
xfree (buffer); xfree (buffer);
leave: leave:
xfree (sockname);
if (ctx) if (ctx)
assuan_release (ctx); assuan_release (ctx);
if (rc)
{ return 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;
} }
/* The thread running the actual check. */
/* Check whether we are still listening on our own socket. In case static void *
* another keyboxd process started after us has taken ownership of our check_own_socket_thread (void *arg)
* 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)
{ {
char *sockname; char *sockname;
npth_t thread;
npth_attr_t tattr;
int err;
if (disable_check_own_socket) (void)arg;
return;
if (check_own_socket_running || shutdown_pending)
return; /* Still running or already shutting down. */
sockname = make_filename_try (gnupg_socketdir (), KEYBOXD_SOCK_NAME, NULL); sockname = make_filename_try (gnupg_socketdir (), KEYBOXD_SOCK_NAME, NULL);
if (!sockname) if (!sockname)
return; /* Out of memory. */ return NULL; /* Out of memory. */
err = npth_attr_init (&tattr); while (!problem_detected)
if (err)
{ {
xfree (sockname); if (shutdown_pending)
return; goto leave;
}
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);
}
gnupg_sleep (CHECK_OWN_SOCKET_INTERVAL);
if (do_check_own_socket (sockname))
problem_detected |= KEYBOXD_PROBLEM_SOCKET_TAKEOVER;
}
keyboxd_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);
/* Check whether the homedir is still available. */
if (gnupg_stat (homedir, &statbuf) && errno == ENOENT)
problem_detected |= KEYBOXD_PROBLEM_HOMEDIR_REMOVED;
}
keyboxd_kick_the_loop ();
leave:
return NULL;
}
/* Figure out whether a keyboxd is available and running. Prints an /* Figure out whether a keyboxd is available and running. Prints an