assuan interface for gpgme
Werner Koch
wk at gnupg.org
Mon Jan 26 12:14:44 CET 2009
Hi!
As you probably know GnuPG uses a simple protocol named Assuan for
interprocess communication between the components. Sometimes it is
necessary to issue commands to gpg-agent which are not available trough
any other tool. For this gpg-connect-agent is a convenient and
scriptable tool.
Sometimes it is required to access for example gpg-agent directly from
an application. To avoid the overhead of running gpg-connect-agent and
prepare an appropriate script is now also possible to use the gpgme
library for this task. Gpgme already uses the Assuan protocol to access
gpgsm and thus all code is already available. The latest SVN version of
gpgme now features a wrapped Assuan_transact call to directly access any
Assuan protocol server. This is still an experimental interface but I
only expect minor API changes before it gets frozen.
The small test program tests/opassuan/tcommand.c can be used to issue
arbitrary command to gpg-agent. For example:
$ ./t-command 'GET_PASSPHRASE --data X X X X'
DATA_CB: datalen=3
assuan command `GET_PASSPHRASE --data X X X X' succeeded
Let's have a look at t-command.c. First we need a couple of callback
functions which are similar to the those used with assuan_transact.
However, a little bit pre-processing has already been done.
static gpg_error_t
data_cb (void *opaque, const void *data, size_t datalen)
{
printf ("DATA_CB: datalen=%d\n", (int)datalen);
return 0;
}
This one is called for all 'D ' lines. DATA,DATLEN is the de-escaped
data line. for large data's you may get a lot of data lines and usually
you want to concatenate them all. OPAQUE is the pointer value you gave
to gpgme_op_assuan_transact for this callback.
static gpg_error_t
inq_cb (void *opaque, const char *name, const char *args,
gpgme_assuan_sendfnc_t sendfnc,
gpgme_assuan_sendfnc_ctx_t sendfnc_value)
{
printf ("INQ_CB: name=`%s' args=`%s'\n", name, args);
return 0;
}
This callback is used for 'INQUIRE ' lines. NAME is the name of the
inquiry and ARGS gives the rest of the line. An inquiry is used by a
server to retrieve additional data from the client. If the client has
this data available it needs to use the SENDFNC to send the data back to
the server like this:
err = sendfnc (sendfnc_value, my_data, my_datalen);
If the server returns an error this error should be the return value of
the inquiry. This send function may be called as often as required.
static gpg_error_t
status_cb (void *opaque, const char *status, const char *args)
{
printf ("STATUS_CB: status=`%s' args=`%s'\n", status, args);
return 0;
}
This is the status callback with the STATUS being the name of the STATUS
and ARGS the rest of the status line.
int
main (int argc, char **argv)
{
gpgme_error_t err;
(Note that gpgme_error_t is the same as gpg_error_t).
gpgme_ctx_t ctx;
const char *command;
Initialize GPGME and get the command.
gpgme_check_version (NULL);
if (argc)
{
argc--;
argv++;
}
command = argc? *argv : "NOP";
Create a new context:
err = gpgme_new (&ctx);
fail_if_err (err);
and set change the protocol from the default to the new ASSUAN protocol.
err = gpgme_set_protocol (ctx, GPGME_PROTOCOL_ASSUAN);
fail_if_err (err);
At this point the context CTX is initialized to access the currently
running gpg-agent. Accessing gpg-agent is the default, to access a
different Assuan server see below.
err = gpgme_op_assuan_transact (ctx, command,
data_cb, NULL,
inq_cb, NULL,
status_cb, NULL);
fail_if_err (err);
The above function closely resembles the assuan_transact function: You
give a context, an Assuan command line and the 3 callback functions. As
usual in gpgme you may instead gpgme_op_assuan_transact_start which is
the asynchronous version of the function.
To make the synchronous and asynchronous versions similar, the error
code from gpgme_op_assuan_transact does only return errors pertaining to
the use Assuan connection and gpgme itself; it does not return the
result of the actual Assuan command (OK or ERR). To get that result you
use:
err = gpgme_op_assuan_result (ctx);
which returns 0 for the 'OK' line and an gpg-error code for the 'ERR
<n>' line. The connection is kept open regardless of this return
value. Now lets print something:
if (err)
fprintf (stderr, "assuan command `%s' failed: %s <%s> (%d)\n",
command, gpg_strerror (err), gpg_strsource (err), err);
else
fprintf (stderr, "assuan command `%s' succeeded\n", command);
and because we do not want to send another command, we close the
connection by releasing the context:
gpgme_release (ctx);
return 0;
}
The remaining question is how to use a different socket than the default
one sued for gpg-agent. We use the engine set_info function to
accomplish this: Right after creating the context you may call
err = gpgme_ctx_set_engine_info (ctx, GPGME_PROTOCOL_ASSUAN,
"/tmp/foo/socket",
"");
to connect to an Assuan server listening on socket /tmp/foo/socket.
Note that the last parameter is an empty string. If you would use NULL,
here the gpgme default value would be used which is an option to run the
initial Assuan commands used for gpg-agent. Right, we are re-using
HOME_DIR parameter to pass flags to the engine.
If you want to test for this feature in configure.ac (but recall that
the API is not yet fixed), you may use this code:
AM_PATH_GPGME("$NEED_GPGME_API:$NEED_GPGME_VERSION",
have_gpgme=yes,have_gpgme=no)
_save_libs=$LIBS
LIBS="$LIBS $GPGME_LIBS"
AC_CHECK_FUNCS([gpgme_op_assuan_transact])
LIBS=$_save_libs
If you want to try this all out, you also need to install the latest
GnuPG from SVN because gpgconf has been changed to return the socket
used by gpg-agent.
Shalom-Salam,
Werner
--
Die Gedanken sind frei. Auschnahme regelt ein Bundeschgesetz.
More information about the Gnupg-devel
mailing list