Coverage Report

Created: 2026-01-09 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gnupg/kbx/kbx-client-util.c
Line
Count
Source
1
/* kbx-client-util.c - Utility functions to implement a keyboxd client
2
 * Copyright (C) 2020 g10 Code GmbH
3
 *
4
 * This file is part of GnuPG.
5
 *
6
 * GnuPG is free software; you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * GnuPG is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
18
 * SPDX-License-Identifier: GPL-3.0+
19
 */
20
21
#include <config.h>
22
#include <stdio.h>
23
#include <stdlib.h>
24
#include <stddef.h>
25
#include <string.h>
26
#include <npth.h>
27
#include <assuan.h>
28
29
#include "../common/util.h"
30
#include "../common/membuf.h"
31
#include "../common/i18n.h"
32
#include "../common/asshelp.h"
33
#include "../common/sysutils.h"
34
#include "../common/exechelp.h"
35
#include "../common/sysutils.h"
36
#include "../common/host2net.h"
37
#include "kbx-client-util.h"
38
39
40
0
#define MAX_DATABLOB_SIZE (16*1024*1024)
41
42
/* Set this to 1 to enable extra debug messages from this module.  */
43
static volatile int debug_client;
44
45
46
/* This object is used to implement a client to the keyboxd.  */
47
struct kbx_client_data_s
48
{
49
  /* The used assuan context.  */
50
  assuan_context_t ctx;
51
52
  /* A stream used to receive data.  If this is NULL D-lines are used
53
   * to receive the data. */
54
  estream_t fp;
55
56
  /* Condition variable to sync the datastream with the command.  */
57
  npth_mutex_t mutex;
58
  npth_cond_t  cond;
59
  npth_t thd;
60
61
  /* The data received from the keyboxd and an error code if there was
62
   * a problem (in which case DATA is also set to NULL.  This is only
63
   * used if FP is not NULL.  */
64
  char *data;
65
  size_t datalen;
66
  gpg_error_t dataerr;
67
68
  /* Helper variables in case D-lines are used (FP is NULL)  */
69
  char *dlinedata;
70
  size_t dlinedatalen;
71
  gpg_error_t dlineerr;
72
};
73
74
75
76
static void *datastream_thread (void *arg);
77
78
79

80
static void
81
lock_datastream (kbx_client_data_t kcd)
82
0
{
83
0
  int rc = npth_mutex_lock (&kcd->mutex);
84
0
  if (rc)
85
0
    log_fatal ("%s: failed to acquire mutex: %s\n", __func__,
86
0
               gpg_strerror (gpg_error_from_errno (rc)));
87
0
}
88
89
90
static void
91
unlock_datastream (kbx_client_data_t kcd)
92
0
{
93
0
  int rc = npth_mutex_unlock (&kcd->mutex);
94
0
  if (rc)
95
0
    log_fatal ("%s: failed to release mutex: %s\n", __func__,
96
0
               gpg_strerror (gpg_error_from_errno (rc)));
97
0
}
98
99
100
101
/* Setup the pipe used for receiving data from the keyboxd.  Store the
102
 * info on KCD.  */
103
static gpg_error_t
104
prepare_data_pipe (kbx_client_data_t kcd)
105
0
{
106
0
  gpg_error_t err;
107
0
  int rc;
108
0
  gnupg_fd_t inpipe;
109
0
  estream_t infp;
110
0
  npth_attr_t tattr;
111
112
0
  kcd->fp = NULL;
113
0
  kcd->data = NULL;
114
0
  kcd->datalen = 0;
115
0
  kcd->dataerr = 0;
116
117
0
  err = gnupg_create_inbound_pipe (&inpipe, &infp, 0);
118
0
  if (err)
119
0
    {
120
0
      log_error ("error creating inbound pipe: %s\n", gpg_strerror (err));
121
0
      return err;  /* That should not happen.  */
122
0
    }
123
124
0
  err = assuan_sendfd (kcd->ctx, inpipe);
125
0
  if (err)
126
0
    {
127
#ifdef HAVE_W32_SYSTEM
128
      log_error ("sending fd %p to keyboxd: %s <%s>\n",
129
                 inpipe, gpg_strerror (err), gpg_strsource (err));
130
#else
131
0
      log_error ("sending fd %d to keyboxd: %s <%s>\n",
132
0
                 inpipe, gpg_strerror (err), gpg_strsource (err));
133
0
#endif
134
0
      es_fclose (infp);
135
#ifdef HAVE_W32_SYSTEM
136
      CloseHandle (inpipe);
137
#else
138
0
      close (inpipe);
139
0
#endif
140
0
      return err;
141
0
    }
142
143
0
  err = assuan_transact (kcd->ctx, "OUTPUT FD",
144
0
                         NULL, NULL, NULL, NULL, NULL, NULL);
145
0
  if (err)
146
0
    {
147
0
      log_info ("keyboxd does not accept our fd: %s <%s>\n",
148
0
                gpg_strerror (err), gpg_strsource (err));
149
0
      es_fclose (infp);
150
0
      return err;
151
0
    }
152
153
#ifdef HAVE_W32_SYSTEM
154
  CloseHandle (inpipe);
155
#else
156
0
  close (inpipe);
157
0
#endif
158
0
  kcd->fp = infp;
159
160
0
  rc = npth_attr_init (&tattr);
161
0
  if (rc)
162
0
    {
163
0
      err = gpg_error_from_errno (rc);
164
0
      log_error ("error preparing thread for keyboxd: %s\n",gpg_strerror (err));
165
0
      es_fclose (infp);
166
0
      kcd->fp = NULL;
167
0
      return err;
168
0
    }
169
0
  npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE);
170
0
  rc = npth_create (&kcd->thd, &tattr, datastream_thread, kcd);
171
0
  if (rc)
172
0
    {
173
0
      err = gpg_error_from_errno (rc);
174
0
      log_error ("error spawning thread for keyboxd: %s\n", gpg_strerror (err));
175
0
      npth_attr_destroy (&tattr);
176
0
      es_fclose (infp);
177
0
      kcd->fp = NULL;
178
0
      return err;
179
0
    }
180
181
0
  npth_attr_destroy (&tattr);
182
0
  return 0;
183
0
}
184
185
186
/* The thread used to read from the data stream.  This is running as
187
 * long as the connection and its datastream exists.  */
188
static void *
189
datastream_thread (void *arg)
190
0
{
191
0
  kbx_client_data_t kcd = arg;
192
0
  gpg_error_t err;
193
0
  int rc;
194
0
  unsigned char lenbuf[4];
195
0
  size_t nread, datalen;
196
0
  char *data = NULL;
197
0
  char *tmpdata;
198
199
0
  if (debug_client)
200
0
    log_debug ("%s: started\n", __func__);
201
0
  while (kcd->fp)
202
0
    {
203
0
      if (debug_client)
204
0
        log_debug ("%s: waiting ...\n", __func__);
205
0
      if (es_read (kcd->fp, lenbuf, 4, &nread))
206
0
        {
207
0
          err = gpg_error_from_syserror ();
208
0
          if (gpg_err_code (err) == GPG_ERR_EAGAIN)
209
0
            continue;
210
0
          log_error ("error reading data length from keyboxd: %s\n",
211
0
                     gpg_strerror (err));
212
0
          gnupg_sleep (1);
213
0
          continue;
214
0
        }
215
0
      if (nread < 4)
216
0
        break;
217
218
0
      datalen = buf32_to_size_t (lenbuf);
219
0
      if (debug_client)
220
0
        log_debug ("%s: keyboxd announced %zu bytes\n", __func__, datalen);
221
0
      if (!datalen)
222
0
        {
223
0
          log_info ("ignoring empty blob received from keyboxd\n");
224
0
          continue;
225
0
        }
226
227
0
      if (datalen > MAX_DATABLOB_SIZE)
228
0
        {
229
0
          err = gpg_error (GPG_ERR_TOO_LARGE);
230
          /* Drop connection or what shall we do?  */
231
0
        }
232
0
      else if (!(data = xtrymalloc (datalen+1)))
233
0
        {
234
0
          err = gpg_error_from_syserror ();
235
0
        }
236
0
      else if (es_read (kcd->fp, data, datalen, &nread))
237
0
        {
238
0
          err = gpg_error_from_syserror ();
239
0
        }
240
0
      else if (datalen != nread)
241
0
        {
242
0
          err = gpg_error (GPG_ERR_TOO_SHORT);
243
0
        }
244
0
      else
245
0
        err = 0;
246
247
0
      if (err)
248
0
        {
249
0
          log_error ("error reading data from keyboxd: %s <%s>\n",
250
0
                     gpg_strerror (err), gpg_strsource (err));
251
0
          xfree (data);
252
0
          data = NULL;
253
0
          datalen = 0;
254
0
        }
255
0
      else
256
0
        {
257
0
          if (debug_client)
258
0
            log_debug ("%s: parsing datastream succeeded\n", __func__);
259
0
        }
260
261
      /* Thread-safe assignment to the result var:  */
262
0
      tmpdata = kcd->data;
263
0
      kcd->data = data;
264
0
      kcd->datalen = datalen;
265
0
      kcd->dataerr = err;
266
0
      xfree (tmpdata);
267
0
      data = NULL;
268
269
      /* Tell the main thread.  */
270
0
      lock_datastream (kcd);
271
0
      rc = npth_cond_signal (&kcd->cond);
272
0
      if (rc)
273
0
        {
274
0
          err = gpg_error_from_errno (rc);
275
0
          log_error ("%s: signaling condition failed: %s\n",
276
0
                     __func__, gpg_strerror (err));
277
0
        }
278
0
      unlock_datastream (kcd);
279
0
    }
280
0
  if (debug_client)
281
0
    log_debug ("%s: finished\n", __func__);
282
283
0
  return NULL;
284
0
}
285
286
287
288
/* Create a new keyboxd client data object and return it at R_KCD.
289
 * CTX is the assuan context to be used for connecting the keyboxd.
290
 * If dlines is set, communication is done without fd passing via
291
 * D-lines.  */
292
gpg_error_t
293
kbx_client_data_new (kbx_client_data_t *r_kcd, assuan_context_t ctx,
294
                     int dlines)
295
0
{
296
0
  kbx_client_data_t kcd;
297
0
  int rc;
298
0
  gpg_error_t err;
299
300
0
  kcd = xtrycalloc (1, sizeof *kcd);
301
0
  if (!kcd)
302
0
    return gpg_error_from_syserror ();
303
304
0
  kcd->ctx = ctx;
305
306
0
  if (dlines)
307
0
    goto leave;
308
309
0
  rc = npth_mutex_init (&kcd->mutex, NULL);
310
0
  if (rc)
311
0
    {
312
0
      err = gpg_error_from_errno (rc);
313
0
      log_error ("error initializing mutex: %s\n", gpg_strerror (err));
314
0
      goto leave; /* Use D-lines.  */
315
0
    }
316
0
  rc = npth_cond_init (&kcd->cond, NULL);
317
0
  if (rc)
318
0
    {
319
0
      err = gpg_error_from_errno (rc);
320
0
      log_error ("error initializing condition: %s\n", gpg_strerror (err));
321
0
      npth_mutex_destroy (&kcd->mutex);
322
0
      goto leave; /* Use D-lines.  */
323
0
    }
324
325
0
  err = prepare_data_pipe (kcd);
326
0
  if (err)
327
0
    {
328
0
      npth_cond_destroy (&kcd->cond);
329
0
      npth_mutex_destroy (&kcd->mutex);
330
      /* Use D-lines.  */
331
0
    }
332
333
0
 leave:
334
0
  *r_kcd = kcd;
335
0
  return 0;
336
0
}
337
338
339
void
340
kbx_client_data_release (kbx_client_data_t kcd)
341
0
{
342
0
  estream_t fp;
343
344
0
  if (!kcd)
345
0
    return;
346
347
0
  fp = kcd->fp;
348
0
  if (!fp)
349
0
    {
350
0
      xfree (kcd);
351
0
      return;
352
0
    }
353
354
0
  if (npth_join (kcd->thd, NULL))
355
0
    log_error ("kbx_client_data_release failed on npth_join");
356
357
0
  kcd->fp = NULL;
358
0
  es_fclose (fp);
359
360
0
  npth_cond_destroy (&kcd->cond);
361
0
  npth_mutex_destroy (&kcd->mutex);
362
0
  xfree (kcd);
363
0
}
364
365
366
/* Send a simple Assuan command to the server.  */
367
gpg_error_t
368
kbx_client_data_simple (kbx_client_data_t kcd, const char *command)
369
0
{
370
0
  if (debug_client)
371
0
    log_debug ("%s: sending command '%s'\n", __func__, command);
372
0
  return assuan_transact (kcd->ctx, command,
373
0
                          NULL, NULL, NULL, NULL, NULL, NULL);
374
0
}
375
376
377
/* Send the COMMAND down to the keyboxd associated with KCD.
378
 * STATUS_CB and STATUS_CB_VALUE are the usual status callback as used
379
 * by assuan_transact.  After this function has returned success
380
 * kbx_client_data_wait needs to be called to actually return the
381
 * data.  */
382
gpg_error_t
383
kbx_client_data_cmd (kbx_client_data_t kcd, const char *command,
384
                     gpg_error_t (*status_cb)(void *opaque, const char *line),
385
                     void *status_cb_value)
386
0
{
387
0
  gpg_error_t err;
388
389
0
  xfree (kcd->dlinedata);
390
0
  kcd->dlinedata = NULL;
391
0
  kcd->dlinedatalen = 0;
392
0
  kcd->dlineerr = 0;
393
394
0
  if (kcd->fp)
395
0
    {
396
0
      if (debug_client)
397
0
        log_debug ("%s: sending command '%s'\n", __func__, command);
398
0
      err = assuan_transact (kcd->ctx, command,
399
0
                             NULL, NULL,
400
0
                             NULL, NULL,
401
0
                             status_cb, status_cb_value);
402
0
      if (err)
403
0
        {
404
0
          if (debug_client
405
0
              && gpg_err_code (err) != GPG_ERR_NOT_FOUND
406
0
              && gpg_err_code (err) != GPG_ERR_NOTHING_FOUND)
407
0
            log_debug ("%s: finished command with error: %s\n",
408
0
                       __func__, gpg_strerror (err));
409
          /* Fixme: On unexpected errors we need a way to cancel the
410
           * data stream.  Probably it will be best to close and
411
           * reopen it.  */
412
0
        }
413
0
    }
414
0
  else /* Slower D-line version if fd-passing is not available.  */
415
0
    {
416
0
      membuf_t mb;
417
0
      size_t len;
418
419
0
      if (debug_client)
420
0
        log_debug ("%s: sending command '%s' (no fd-passing)\n",
421
0
                   __func__, command);
422
0
      init_membuf (&mb, 8192);
423
0
      err = assuan_transact (kcd->ctx, command,
424
0
                             put_membuf_cb, &mb,
425
0
                             NULL, NULL,
426
0
                             status_cb, status_cb_value);
427
0
      if (err)
428
0
        {
429
0
          if (debug_client
430
0
              && gpg_err_code (err) != GPG_ERR_NOT_FOUND
431
0
              && gpg_err_code (err) != GPG_ERR_NOTHING_FOUND)
432
0
            log_debug ("%s: finished command with error: %s\n",
433
0
                       __func__, gpg_strerror (err));
434
0
          xfree (get_membuf (&mb, &len));
435
0
          kcd->dlineerr = err;
436
0
          goto leave;
437
0
        }
438
439
0
      kcd->dlinedata = get_membuf (&mb, &kcd->dlinedatalen);
440
0
      if (!kcd->dlinedata)
441
0
        {
442
0
          err = gpg_error_from_syserror ();
443
0
          goto leave;
444
0
        }
445
0
    }
446
447
0
 leave:
448
0
  return err;
449
0
}
450
451
452
453
/* Wait for the data from the server and on success return it at
454
 * (R_DATA, R_DATALEN). */
455
gpg_error_t
456
kbx_client_data_wait (kbx_client_data_t kcd, char **r_data, size_t *r_datalen)
457
0
{
458
0
  gpg_error_t err = 0;
459
0
  int rc;
460
461
0
  *r_data = NULL;
462
0
  *r_datalen = 0;
463
0
  if (kcd->fp)
464
0
    {
465
0
      lock_datastream (kcd);
466
0
      if (!kcd->data && !kcd->dataerr)
467
0
        {
468
0
          if (debug_client)
469
0
            log_debug ("%s: waiting on datastream_cond ...\n", __func__);
470
0
          rc = npth_cond_wait (&kcd->cond, &kcd->mutex);
471
0
          if (rc)
472
0
            {
473
0
              err = gpg_error_from_errno (rc);
474
0
              log_error ("%s: waiting on condition failed: %s\n",
475
0
                         __func__, gpg_strerror (err));
476
0
            }
477
0
          else if (debug_client)
478
0
            log_debug ("%s: waiting on datastream.cond done\n", __func__);
479
0
        }
480
0
      *r_data = kcd->data;
481
0
      kcd->data = NULL;
482
0
      *r_datalen = kcd->datalen;
483
0
      err = err? err : kcd->dataerr;
484
485
0
      unlock_datastream (kcd);
486
0
    }
487
0
  else
488
0
    {
489
0
      *r_data = kcd->dlinedata;
490
0
      kcd->dlinedata = NULL;
491
0
      *r_datalen = kcd->dlinedatalen;
492
0
      err = kcd->dlineerr;
493
0
    }
494
495
0
  return err;
496
0
}