Coverage Report

Created: 2022-12-08 06:10

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