Some tools to play with GnuPG’s smartcard daemon and OpenPGP smartcards.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
scdtools/src/gpg-util.c

261 lines
7.3 KiB

/*
* scdtools - Tools for Scdaemon and OpenPGP smartcards
* Copyright (C) 2014 Damien Goutte-Gattat
*
* This program 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.
*
* This program 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 <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <gcrypt.h>
#include "gpg-util.h"
/*
* Find the socket of GnuPG Agent. First look for the GPG_AGENT_INFO
* environment variable, if unset look for the standard socket in
* GnuPG's home directory, if not found use gpg-connect-agent.
*
* The socket name is stored in a newly allocated buffer that should
* be freed by the caller.
*
* Return NULL if the socket could not be found.
*/
static char *
get_agent_socket_name(void)
{
char *env_info, *socket_name = NULL;
/* First look for GPG_AGENT_INFO in the environment. */
if ( (env_info = getenv("GPG_AGENT_INFO")) ) {
if ( (socket_name = strdup(env_info)) ) {
char *colon_index;
/*
* GPG_AGENT_INFO should be of the form
* "path_to_socket:x:y", where x is the PID
* of the GnuPG Agent process and y is the
* protocol version (currently 1).
*/
if ( (colon_index = strchr(socket_name, ':')) )
*colon_index = '\0';
}
}
else {
/* Then look for the standard socket. */
char buf[255] = "";
struct passwd *pwd;
struct stat st;
if ( (env_info = getenv("GNUPGHOME")) )
snprintf(buf, sizeof(buf), "%s/S.gpg-agent", env_info);
else if ( (env_info = getenv("HOME")) )
snprintf(buf, sizeof(buf), "%s/.gnupg/S.gpg-agent", env_info);
else if ( (pwd = getpwuid(getuid())) )
snprintf(buf, sizeof(buf), "%s/.gnupg/S.gpg-agent", pwd->pw_dir);
/* Check whether the socket does exist. */
if ( *buf && stat(buf, &st) != -1 && S_ISSOCK(st.st_mode) )
socket_name = strdup(buf);
else {
/*
* Finally, try to ask the agent directly. This method could
* replace the two above, but since it requires spawning a
* new process, we use it only as a fallback. We expect that
* most of the time, the agent should already be running when
* our programs are called, and thus the socket would already
* exist and be found by the above code.
*/
FILE *f;
f = popen("gpg-connect-agent 'GETINFO socket_name' /bye", "r");
if ( f ) {
if ( fscanf(f, "D %254s\nOK\n", buf) == 1 )
socket_name = strdup(buf);
pclose(f);
}
}
}
return socket_name;
}
/*
* Establish a connection with a running GnuPG Agent.
*
* @param ctx A pointer to a assuan_context_t object that will
* be initialized by this function.
* @param init_env If true, environment variables needed for pinentry
* will be passed to the agent.
*
* @return 0 if the connection was established, or a gpg_error_t
* error code.
*/
gpg_error_t
connect_to_agent(assuan_context_t *ctx, int init_env)
{
char *socket_name;
gpg_error_t e;
if ( ! (socket_name = get_agent_socket_name()) )
return gcry_error(GPG_ERR_NO_AGENT);
if ( ! (e = assuan_new(ctx)) ) {
if ( ! (e = assuan_socket_connect(*ctx, socket_name, ASSUAN_INVALID_PID, 0)) ) {
if ( init_env && (e = init_agent_environment(*ctx)) )
assuan_release(*ctx);
}
else
assuan_release(*ctx);
}
free(socket_name);
return e;
}
/*
* Callback for the below function.
*/
static gpg_error_t
socket_name_cb(void *arg, const void *line, size_t len)
{
char **socket_name = (char **)arg;
(void)len;
if ( ! (*socket_name = strdup(line)) )
return gcry_error_from_errno(errno);
return 0;
}
/*
* Find the socket of Scdaemon. Attempt to connect to a running
* GnuPG Agent and inquire about Scdaemon's running status and
* socket name.
*
* @param socket_name A pointer to a string to store the name of
* Scdaemon's socket. Should be freed by the
* caller after use.
*
* @return 0 if Scdaemon's socket was found, or a gpg_error_t
* error code otherwise.
*/
static gpg_error_t
get_scd_socket_name(char **scd_socket_name)
{
assuan_context_t ctx;
gpg_error_t e;
if ( ! (e = connect_to_agent(&ctx, 0)) ) {
if ( ! (e = assuan_transact(ctx, "GETINFO scd_running",
NULL, NULL, NULL, NULL, NULL, NULL)) ) {
e = assuan_transact(ctx, "SCD GETINFO socket_name",
socket_name_cb, scd_socket_name, NULL, NULL, NULL, NULL);
}
assuan_release(ctx);
}
return e;
}
/*
* Establish a connection with a running Scdaemon.
*
* @param ctx A pointer to a assuan_context_t object that will be
* initialized by this function.
*
* @return 0 if the connection was established, or a gpg_error_t
* error code.
*/
gpg_error_t
connect_to_scdaemon(assuan_context_t *ctx)
{
char *socket_name;
gpg_error_t e;
if ( (e = get_scd_socket_name(&socket_name)) )
return e;
if ( ! (e = assuan_new(ctx)) ) {
if ( ! (e = assuan_socket_connect(*ctx, socket_name, ASSUAN_INVALID_PID, 0)) )
e = gcry_error(GPG_ERR_NO_ERROR);
else
assuan_release(*ctx);
}
return e;
}
/*
* Pass to the GnuPG Agent the environment variables needed for
* pinentry.
*
* @param ctx The assuan_context_t object used to communicate with
* a running GnuPG Agent.
*
* @return 0 if all environment variables were passed successfully,
* or a gpg_error_t error code.
*/
gpg_error_t
init_agent_environment(assuan_context_t ctx)
{
char command[64], *value;
gpg_error_t e;
struct {
const char *name;
const char *option;
} *var, env_vars[] = {
{ "GPG_TTY", "ttyname" },
{ "TERM", "ttytype" },
{ "DISPLAY", "display" },
{ "XAUTHORITY", "xauthority" },
{ "PINENTRY_USER_DATA", "pinentry-user-data" },
{ NULL, NULL }
};
for ( var = env_vars, e = 0; var->name && e == 0; var++ ) {
if ( (value = getenv(var->name)) ) {
if ( (unsigned) snprintf(command, sizeof(command), "OPTION %s=%s",
var->option, value) >= sizeof(command) )
e = gcry_error(GPG_ERR_TOO_LARGE);
e = assuan_transact(ctx, command, NULL, NULL, NULL, NULL,
NULL, NULL);
}
}
return e;
}