Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F19741972
kbx-client-util.c
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
12 KB
Subscribers
None
kbx-client-util.c
View Options
/* kbx-client-util.c - Utility functions to implement a keyboxd client
* Copyright (C) 2020 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0+
*/
#include
<config.h>
#include
<stdio.h>
#include
<stdlib.h>
#include
<stddef.h>
#include
<string.h>
#include
<npth.h>
#include
<assuan.h>
#include
"../common/util.h"
#include
"../common/membuf.h"
#include
"../common/i18n.h"
#include
"../common/asshelp.h"
#include
"../common/sysutils.h"
#include
"../common/exechelp.h"
#include
"../common/sysutils.h"
#include
"../common/host2net.h"
#include
"kbx-client-util.h"
#define MAX_DATABLOB_SIZE (16*1024*1024)
/* This object is used to implement a client to the keyboxd. */
struct
kbx_client_data_s
{
/* The used assuan context. */
assuan_context_t
ctx
;
/* A stream used to receive data. If this is NULL D-lines are used
* to receive the data. */
estream_t
fp
;
/* Condition variable to sync the datastream with the command. */
npth_mutex_t
mutex
;
npth_cond_t
cond
;
npth_t
thd
;
/* The data received from the keyboxd and an error code if there was
* a problem (in which case DATA is also set to NULL. This is only
* used if FP is not NULL. */
char
*
data
;
size_t
datalen
;
gpg_error_t
dataerr
;
/* Helper variables in case D-lines are used (FP is NULL) */
char
*
dlinedata
;
size_t
dlinedatalen
;
gpg_error_t
dlineerr
;
};
static
void
*
datastream_thread
(
void
*
arg
);
static
void
lock_datastream
(
kbx_client_data_t
kcd
)
{
int
rc
=
npth_mutex_lock
(
&
kcd
->
mutex
);
if
(
rc
)
log_fatal
(
"%s: failed to acquire mutex: %s
\n
"
,
__func__
,
gpg_strerror
(
gpg_error_from_errno
(
rc
)));
}
static
void
unlock_datastream
(
kbx_client_data_t
kcd
)
{
int
rc
=
npth_mutex_unlock
(
&
kcd
->
mutex
);
if
(
rc
)
log_fatal
(
"%s: failed to release mutex: %s
\n
"
,
__func__
,
gpg_strerror
(
gpg_error_from_errno
(
rc
)));
}
/* Setup the pipe used for receiving data from the keyboxd. Store the
* info on KCD. */
static
gpg_error_t
prepare_data_pipe
(
kbx_client_data_t
kcd
)
{
gpg_error_t
err
;
int
rc
;
gnupg_fd_t
inpipe
;
estream_t
infp
;
npth_attr_t
tattr
;
kcd
->
fp
=
NULL
;
kcd
->
data
=
NULL
;
kcd
->
datalen
=
0
;
kcd
->
dataerr
=
0
;
err
=
gnupg_create_inbound_pipe
(
&
inpipe
,
&
infp
,
0
);
if
(
err
)
{
log_error
(
"error creating inbound pipe: %s
\n
"
,
gpg_strerror
(
err
));
return
err
;
/* That should not happen. */
}
err
=
assuan_sendfd
(
kcd
->
ctx
,
inpipe
);
if
(
err
)
{
#ifdef HAVE_W32_SYSTEM
log_error
(
"sending fd %p to keyboxd: %s <%s>
\n
"
,
inpipe
,
gpg_strerror
(
err
),
gpg_strsource
(
err
));
#else
log_error
(
"sending fd %d to keyboxd: %s <%s>
\n
"
,
inpipe
,
gpg_strerror
(
err
),
gpg_strsource
(
err
));
#endif
es_fclose
(
infp
);
#ifdef HAVE_W32_SYSTEM
CloseHandle
(
inpipe
);
#else
close
(
inpipe
);
#endif
return
err
;
}
err
=
assuan_transact
(
kcd
->
ctx
,
"OUTPUT FD"
,
NULL
,
NULL
,
NULL
,
NULL
,
NULL
,
NULL
);
if
(
err
)
{
log_info
(
"keyboxd does not accept our fd: %s <%s>
\n
"
,
gpg_strerror
(
err
),
gpg_strsource
(
err
));
es_fclose
(
infp
);
return
err
;
}
#ifdef HAVE_W32_SYSTEM
CloseHandle
(
inpipe
);
#else
close
(
inpipe
);
#endif
kcd
->
fp
=
infp
;
rc
=
npth_attr_init
(
&
tattr
);
if
(
rc
)
{
err
=
gpg_error_from_errno
(
rc
);
log_error
(
"error preparing thread for keyboxd: %s
\n
"
,
gpg_strerror
(
err
));
es_fclose
(
infp
);
kcd
->
fp
=
NULL
;
return
err
;
}
npth_attr_setdetachstate
(
&
tattr
,
NPTH_CREATE_JOINABLE
);
rc
=
npth_create
(
&
kcd
->
thd
,
&
tattr
,
datastream_thread
,
kcd
);
if
(
rc
)
{
err
=
gpg_error_from_errno
(
rc
);
log_error
(
"error spawning thread for keyboxd: %s
\n
"
,
gpg_strerror
(
err
));
npth_attr_destroy
(
&
tattr
);
es_fclose
(
infp
);
kcd
->
fp
=
NULL
;
return
err
;
}
npth_attr_destroy
(
&
tattr
);
return
0
;
}
/* The thread used to read from the data stream. This is running as
* long as the connection and its datastream exists. */
static
void
*
datastream_thread
(
void
*
arg
)
{
kbx_client_data_t
kcd
=
arg
;
gpg_error_t
err
;
int
rc
;
unsigned
char
lenbuf
[
4
];
size_t
nread
,
datalen
;
char
*
data
=
NULL
;
char
*
tmpdata
;
/* log_debug ("%s: started\n", __func__); */
while
(
kcd
->
fp
)
{
/* log_debug ("%s: waiting ...\n", __func__); */
if
(
es_read
(
kcd
->
fp
,
lenbuf
,
4
,
&
nread
))
{
err
=
gpg_error_from_syserror
();
if
(
gpg_err_code
(
err
)
==
GPG_ERR_EAGAIN
)
continue
;
log_error
(
"error reading data length from keyboxd: %s
\n
"
,
gpg_strerror
(
err
));
gnupg_sleep
(
1
);
continue
;
}
if
(
nread
<
4
)
break
;
datalen
=
buf32_to_size_t
(
lenbuf
);
/* log_debug ("keyboxd announced %zu bytes\n", datalen); */
if
(
!
datalen
)
{
log_info
(
"ignoring empty blob received from keyboxd
\n
"
);
continue
;
}
if
(
datalen
>
MAX_DATABLOB_SIZE
)
{
err
=
gpg_error
(
GPG_ERR_TOO_LARGE
);
/* Drop connection or what shall we do? */
}
else
if
(
!
(
data
=
xtrymalloc
(
datalen
+
1
)))
{
err
=
gpg_error_from_syserror
();
}
else
if
(
es_read
(
kcd
->
fp
,
data
,
datalen
,
&
nread
))
{
err
=
gpg_error_from_syserror
();
}
else
if
(
datalen
!=
nread
)
{
err
=
gpg_error
(
GPG_ERR_TOO_SHORT
);
}
else
err
=
0
;
if
(
err
)
{
log_error
(
"error reading data from keyboxd: %s <%s>
\n
"
,
gpg_strerror
(
err
),
gpg_strsource
(
err
));
xfree
(
data
);
data
=
NULL
;
datalen
=
0
;
}
else
{
/* log_debug ("parsing datastream succeeded\n"); */
}
/* Thread-safe assignment to the result var: */
tmpdata
=
kcd
->
data
;
kcd
->
data
=
data
;
kcd
->
datalen
=
datalen
;
kcd
->
dataerr
=
err
;
xfree
(
tmpdata
);
data
=
NULL
;
/* Tell the main thread. */
lock_datastream
(
kcd
);
rc
=
npth_cond_signal
(
&
kcd
->
cond
);
if
(
rc
)
{
err
=
gpg_error_from_errno
(
rc
);
log_error
(
"%s: signaling condition failed: %s
\n
"
,
__func__
,
gpg_strerror
(
err
));
}
unlock_datastream
(
kcd
);
}
/* log_debug ("%s: finished\n", __func__); */
return
NULL
;
}
/* Create a new keyboxd client data object and return it at R_KCD.
* CTX is the assuan context to be used for connecting the keyboxd.
* If dlines is set, communication is done without fd passing via
* D-lines. */
gpg_error_t
kbx_client_data_new
(
kbx_client_data_t
*
r_kcd
,
assuan_context_t
ctx
,
int
dlines
)
{
kbx_client_data_t
kcd
;
int
rc
;
gpg_error_t
err
;
kcd
=
xtrycalloc
(
1
,
sizeof
*
kcd
);
if
(
!
kcd
)
return
gpg_error_from_syserror
();
kcd
->
ctx
=
ctx
;
if
(
dlines
)
goto
leave
;
rc
=
npth_mutex_init
(
&
kcd
->
mutex
,
NULL
);
if
(
rc
)
{
err
=
gpg_error_from_errno
(
rc
);
log_error
(
"error initializing mutex: %s
\n
"
,
gpg_strerror
(
err
));
goto
leave
;
/* Use D-lines. */
}
rc
=
npth_cond_init
(
&
kcd
->
cond
,
NULL
);
if
(
rc
)
{
err
=
gpg_error_from_errno
(
rc
);
log_error
(
"error initializing condition: %s
\n
"
,
gpg_strerror
(
err
));
npth_mutex_destroy
(
&
kcd
->
mutex
);
goto
leave
;
/* Use D-lines. */
}
err
=
prepare_data_pipe
(
kcd
);
if
(
err
)
{
npth_cond_destroy
(
&
kcd
->
cond
);
npth_mutex_destroy
(
&
kcd
->
mutex
);
/* Use D-lines. */
}
leave
:
*
r_kcd
=
kcd
;
return
0
;
}
void
kbx_client_data_release
(
kbx_client_data_t
kcd
)
{
estream_t
fp
;
if
(
!
kcd
)
return
;
fp
=
kcd
->
fp
;
if
(
!
fp
)
{
xfree
(
kcd
);
return
;
}
if
(
npth_join
(
kcd
->
thd
,
NULL
))
log_error
(
"kbx_client_data_release failed on npth_join"
);
kcd
->
fp
=
NULL
;
es_fclose
(
fp
);
npth_cond_destroy
(
&
kcd
->
cond
);
npth_mutex_destroy
(
&
kcd
->
mutex
);
xfree
(
kcd
);
}
/* Send a simple Assuan command to the server. */
gpg_error_t
kbx_client_data_simple
(
kbx_client_data_t
kcd
,
const
char
*
command
)
{
/* log_debug ("%s: sending command '%s'\n", __func__, command); */
return
assuan_transact
(
kcd
->
ctx
,
command
,
NULL
,
NULL
,
NULL
,
NULL
,
NULL
,
NULL
);
}
/* Send the COMMAND down to the keyboxd associated with KCD.
* STATUS_CB and STATUS_CB_VALUE are the usual status callback as used
* by assuan_transact. After this function has returned success
* kbx_client_data_wait needs to be called to actually return the
* data. */
gpg_error_t
kbx_client_data_cmd
(
kbx_client_data_t
kcd
,
const
char
*
command
,
gpg_error_t
(
*
status_cb
)(
void
*
opaque
,
const
char
*
line
),
void
*
status_cb_value
)
{
gpg_error_t
err
;
xfree
(
kcd
->
dlinedata
);
kcd
->
dlinedata
=
NULL
;
kcd
->
dlinedatalen
=
0
;
kcd
->
dlineerr
=
0
;
if
(
kcd
->
fp
)
{
/* log_debug ("%s: sending command '%s'\n", __func__, command); */
err
=
assuan_transact
(
kcd
->
ctx
,
command
,
NULL
,
NULL
,
NULL
,
NULL
,
status_cb
,
status_cb_value
);
if
(
err
)
{
if
(
gpg_err_code
(
err
)
!=
GPG_ERR_NOT_FOUND
&&
gpg_err_code
(
err
)
!=
GPG_ERR_NOTHING_FOUND
)
log_debug
(
"%s: finished command with error: %s
\n
"
,
__func__
,
gpg_strerror
(
err
));
/* Fixme: On unexpected errors we need a way to cancel the
* data stream. Probably it will be best to close and
* reopen it. */
}
}
else
/* Slower D-line version if fd-passing is not available. */
{
membuf_t
mb
;
size_t
len
;
/* log_debug ("%s: sending command '%s' (no fd-passing)\n", */
/* __func__, command); */
init_membuf
(
&
mb
,
8192
);
err
=
assuan_transact
(
kcd
->
ctx
,
command
,
put_membuf_cb
,
&
mb
,
NULL
,
NULL
,
status_cb
,
status_cb_value
);
if
(
err
)
{
/* if (gpg_err_code (err) != GPG_ERR_NOT_FOUND */
/* && gpg_err_code (err) != GPG_ERR_NOTHING_FOUND) */
/* log_debug ("%s: finished command with error: %s\n", */
/* __func__, gpg_strerror (err)); */
xfree
(
get_membuf
(
&
mb
,
&
len
));
kcd
->
dlineerr
=
err
;
goto
leave
;
}
kcd
->
dlinedata
=
get_membuf
(
&
mb
,
&
kcd
->
dlinedatalen
);
if
(
!
kcd
->
dlinedata
)
{
err
=
gpg_error_from_syserror
();
goto
leave
;
}
}
leave
:
return
err
;
}
/* Wait for the data from the server and on success return it at
* (R_DATA, R_DATALEN). */
gpg_error_t
kbx_client_data_wait
(
kbx_client_data_t
kcd
,
char
**
r_data
,
size_t
*
r_datalen
)
{
gpg_error_t
err
=
0
;
int
rc
;
*
r_data
=
NULL
;
*
r_datalen
=
0
;
if
(
kcd
->
fp
)
{
lock_datastream
(
kcd
);
if
(
!
kcd
->
data
&&
!
kcd
->
dataerr
)
{
/* log_debug ("%s: waiting on datastream_cond ...\n", __func__); */
rc
=
npth_cond_wait
(
&
kcd
->
cond
,
&
kcd
->
mutex
);
if
(
rc
)
{
err
=
gpg_error_from_errno
(
rc
);
log_error
(
"%s: waiting on condition failed: %s
\n
"
,
__func__
,
gpg_strerror
(
err
));
}
/* else */
/* log_debug ("%s: waiting on datastream.cond done\n", __func__); */
}
*
r_data
=
kcd
->
data
;
kcd
->
data
=
NULL
;
*
r_datalen
=
kcd
->
datalen
;
err
=
err
?
err
:
kcd
->
dataerr
;
unlock_datastream
(
kcd
);
}
else
{
*
r_data
=
kcd
->
dlinedata
;
kcd
->
dlinedata
=
NULL
;
*
r_datalen
=
kcd
->
dlinedatalen
;
err
=
kcd
->
dlineerr
;
}
return
err
;
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Sat, Feb 1, 9:30 AM (1 d, 13 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
93/ae/a14403f475612960c81b3781c0c8
Attached To
rG GnuPG
Event Timeline
Log In to Comment