#ifndef WIN32
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <apr_pools.h>
#include <apr_strings.h>
#include <apr_user.h>
#include "svn_auth.h"
#include "svn_config.h"
#include "svn_error.h"
#include "svn_io.h"
#include "svn_pools.h"
#include "svn_cmdline.h"
#include "svn_checksum.h"
#include "svn_string.h"
#include "svn_hash.h"
#include "svn_user.h"
#include "svn_dirent_uri.h"
#include "auth.h"
#include "private/svn_auth_private.h"
#include "svn_private_config.h"
#ifdef SVN_HAVE_GPG_AGENT
#define BUFFER_SIZE 1024
#define ATTEMPT_PARAMETER "svn.simple.gpg_agent.attempt"
static char *
escape_blanks(char *str)
{
char *s = str;
while (*s)
{
if (*s == ' ')
*s = '+';
s++;
}
return str;
}
#define is_hex(c) (((c) >= '0' && (c) <= '9') || ((c) >= 'A' && (c) <= 'F'))
#define hex_to_int(c) ((c) < '9' ? (c) - '0' : (c) - 'A' + 10)
static char *
unescape_assuan(char *str)
{
char *s = str;
while (s[0])
{
if (s[0] == '%' && is_hex(s[1]) && is_hex(s[2]))
{
char *s2 = s;
char val = hex_to_int(s[1]) * 16 + hex_to_int(s[2]);
s2[0] = val;
++s2;
while (s2[2])
{
s2[0] = s2[2];
++s2;
}
s2[0] = '\0';
}
++s;
}
return str;
}
static svn_error_t *
get_cache_id(const char **cache_id_p, const char *realmstring,
apr_pool_t *result_pool, apr_pool_t *scratch_pool)
{
const char *cache_id = NULL;
svn_checksum_t *digest = NULL;
SVN_ERR(svn_checksum(&digest, svn_checksum_md5, realmstring,
strlen(realmstring), scratch_pool));
cache_id = svn_checksum_to_cstring(digest, result_pool);
*cache_id_p = cache_id;
return SVN_NO_ERROR;
}
static svn_boolean_t
receive_from_gpg_agent(int sd, char *buf, size_t n)
{
int i = 0;
size_t recvd;
char c;
if (n > 0)
*buf = '\0';
while (i < n)
{
recvd = read(sd, &c, 1);
if (recvd == -1)
return FALSE;
buf[i] = c;
i++;
if (i < n && c == '\n')
{
buf[i] = '\0';
return TRUE;
}
}
return FALSE;
}
static svn_boolean_t
send_option(int sd, char *buf, size_t n, const char *option, const char *value,
apr_pool_t *scratch_pool)
{
const char *request;
request = apr_psprintf(scratch_pool, "OPTION %s=%s\n", option, value);
if (write(sd, request, strlen(request)) == -1)
return FALSE;
if (!receive_from_gpg_agent(sd, buf, n))
return FALSE;
return (strncmp(buf, "OK", 2) == 0);
}
static void
bye_gpg_agent(int sd)
{
write(sd, "BYE\n", 4);
close(sd);
}
static const char *
find_gpg_agent_socket(apr_pool_t *result_pool, apr_pool_t *scratch_pool)
{
char *gpg_agent_info = NULL;
char *gnupghome = NULL;
const char *socket_name = NULL;
if ((gpg_agent_info = getenv("GPG_AGENT_INFO")) != NULL)
{
apr_array_header_t *socket_details;
socket_details = svn_cstring_split(gpg_agent_info, ":", TRUE,
scratch_pool);
socket_name = APR_ARRAY_IDX(socket_details, 0, const char *);
}
else if ((gnupghome = getenv("GNUPGHOME")) != NULL)
{
const char *homedir = svn_dirent_canonicalize(gnupghome, scratch_pool);
socket_name = svn_dirent_join(homedir, "S.gpg-agent", scratch_pool);
}
else
{
int i = 0;
const char *maybe_socket[] = {NULL, NULL, NULL, NULL};
const char *homedir;
#ifdef APR_HAS_USER
apr_uid_t uid;
apr_gid_t gid;
if (apr_uid_current(&uid, &gid, scratch_pool) == APR_SUCCESS)
{
const char *uidbuf = apr_psprintf(scratch_pool, "%lu",
(unsigned long)uid);
maybe_socket[i++] = svn_dirent_join_many(scratch_pool, "/run/user",
uidbuf, "gnupg",
"S.gpg-agent",
SVN_VA_NULL);
maybe_socket[i++] = svn_dirent_join_many(scratch_pool,
"/var/run/user",
uidbuf, "gnupg",
"S.gpg-agent",
SVN_VA_NULL);
}
#endif
homedir = svn_user_get_homedir(scratch_pool);
if (homedir)
maybe_socket[i++] = svn_dirent_join_many(scratch_pool, homedir,
".gnupg", "S.gpg-agent",
SVN_VA_NULL);
for (i = 0; !socket_name && maybe_socket[i]; i++)
{
apr_finfo_t finfo;
svn_error_t *err = svn_io_stat(&finfo, maybe_socket[i],
APR_FINFO_TYPE, scratch_pool);
if (!err && finfo.filetype == APR_SOCK)
socket_name = maybe_socket[i];
svn_error_clear(err);
}
}
if (socket_name)
socket_name = apr_pstrdup(result_pool, socket_name);
return socket_name;
}
static svn_error_t *
find_running_gpg_agent(int *new_sd, apr_pool_t *pool)
{
char *buffer;
const char *socket_name = find_gpg_agent_socket(pool, pool);
const char *request = NULL;
const char *p = NULL;
char *ep = NULL;
int sd;
*new_sd = -1;
if (socket_name != NULL)
{
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, socket_name, sizeof(addr.sun_path) - 1);
addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
sd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sd == -1)
return SVN_NO_ERROR;
if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
{
close(sd);
return SVN_NO_ERROR;
}
}
else
return SVN_NO_ERROR;
buffer = apr_palloc(pool, BUFFER_SIZE);
if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
{
bye_gpg_agent(sd);
return SVN_NO_ERROR;
}
if (strncmp(buffer, "OK", 2) != 0)
{
bye_gpg_agent(sd);
return SVN_NO_ERROR;
}
request = "GETINFO socket_name\n";
if (write(sd, request, strlen(request)) == -1)
{
bye_gpg_agent(sd);
return SVN_NO_ERROR;
}
if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
{
bye_gpg_agent(sd);
return SVN_NO_ERROR;
}
if (strncmp(buffer, "D", 1) == 0)
p = &buffer[2];
if (!p)
{
bye_gpg_agent(sd);
return SVN_NO_ERROR;
}
ep = strchr(p, '\n');
if (ep != NULL)
*ep = '\0';
if (strcmp(socket_name, p) != 0)
{
bye_gpg_agent(sd);
return SVN_NO_ERROR;
}
if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
{
bye_gpg_agent(sd);
return SVN_NO_ERROR;
}
if (strncmp(buffer, "OK", 2) != 0)
{
bye_gpg_agent(sd);
return SVN_NO_ERROR;
}
*new_sd = sd;
return SVN_NO_ERROR;
}
static svn_boolean_t
send_options(int sd, char *buf, size_t n, apr_pool_t *scratch_pool)
{
const char *tty_name;
const char *tty_type;
const char *lc_ctype;
const char *display;
tty_name = getenv("GPG_TTY");
if (tty_name != NULL)
{
if (!send_option(sd, buf, n, "ttyname", tty_name, scratch_pool))
return FALSE;
}
tty_type = getenv("TERM");
if (tty_type != NULL)
{
if (!send_option(sd, buf, n, "ttytype", tty_type, scratch_pool))
return FALSE;
}
lc_ctype = getenv("LC_ALL");
if (lc_ctype == NULL)
lc_ctype = getenv("LC_CTYPE");
if (lc_ctype == NULL)
lc_ctype = getenv("LANG");
if (lc_ctype != NULL)
{
if (!send_option(sd, buf, n, "lc-ctype", lc_ctype, scratch_pool))
return FALSE;
}
display = getenv("DISPLAY");
if (display != NULL)
{
if (!send_option(sd, buf, n, "display", display, scratch_pool))
return FALSE;
}
return TRUE;
}
static svn_error_t *
password_get_gpg_agent(svn_boolean_t *done,
const char **password,
apr_hash_t *creds,
const char *realmstring,
const char *username,
apr_hash_t *parameters,
svn_boolean_t non_interactive,
apr_pool_t *pool)
{
int sd;
char *p = NULL;
char *ep = NULL;
char *buffer;
const char *request = NULL;
const char *cache_id = NULL;
char *password_prompt;
char *realm_prompt;
char *error_prompt;
int *attempt;
*done = FALSE;
attempt = svn_hash_gets(parameters, ATTEMPT_PARAMETER);
SVN_ERR(find_running_gpg_agent(&sd, pool));
if (sd == -1)
return SVN_NO_ERROR;
buffer = apr_palloc(pool, BUFFER_SIZE);
if (!send_options(sd, buffer, BUFFER_SIZE, pool))
{
bye_gpg_agent(sd);
return SVN_NO_ERROR;
}
SVN_ERR(get_cache_id(&cache_id, realmstring, pool, pool));
password_prompt = apr_psprintf(pool, _("Password for '%s': "), username);
realm_prompt = apr_psprintf(pool, _("Enter your Subversion password for %s"),
realmstring);
if (*attempt == 1)
error_prompt = apr_pstrdup(pool, "X");
else
error_prompt = apr_pstrdup(pool, _("Authentication failed"));
request = apr_psprintf(pool,
"GET_PASSPHRASE --data %s"
"%s %s %s %s\n",
non_interactive ? "--no-ask " : "",
cache_id,
escape_blanks(error_prompt),
escape_blanks(password_prompt),
escape_blanks(realm_prompt));
if (write(sd, request, strlen(request)) == -1)
{
bye_gpg_agent(sd);
return SVN_NO_ERROR;
}
if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
{
bye_gpg_agent(sd);
return SVN_NO_ERROR;
}
bye_gpg_agent(sd);
if (strncmp(buffer, "ERR", 3) == 0)
return SVN_NO_ERROR;
p = NULL;
if (strncmp(buffer, "D", 1) == 0)
p = &buffer[2];
if (!p)
return SVN_NO_ERROR;
ep = strchr(p, '\n');
if (ep != NULL)
*ep = '\0';
*password = unescape_assuan(p);
*done = TRUE;
return SVN_NO_ERROR;
}
static svn_error_t *
password_set_gpg_agent(svn_boolean_t *done,
apr_hash_t *creds,
const char *realmstring,
const char *username,
const char *password,
apr_hash_t *parameters,
svn_boolean_t non_interactive,
apr_pool_t *pool)
{
int sd;
*done = FALSE;
SVN_ERR(find_running_gpg_agent(&sd, pool));
if (sd == -1)
return SVN_NO_ERROR;
bye_gpg_agent(sd);
*done = TRUE;
return SVN_NO_ERROR;
}
static svn_error_t *
simple_gpg_agent_first_creds(void **credentials,
void **iter_baton,
void *provider_baton,
apr_hash_t *parameters,
const char *realmstring,
apr_pool_t *pool)
{
svn_error_t *err;
int *attempt = apr_palloc(pool, sizeof(*attempt));
*attempt = 1;
svn_hash_sets(parameters, ATTEMPT_PARAMETER, attempt);
err = svn_auth__simple_creds_cache_get(credentials, iter_baton,
provider_baton, parameters,
realmstring, password_get_gpg_agent,
SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
pool);
*iter_baton = attempt;
return err;
}
static svn_error_t *
simple_gpg_agent_next_creds(void **credentials,
void *iter_baton,
void *provider_baton,
apr_hash_t *parameters,
const char *realmstring,
apr_pool_t *pool)
{
int *attempt = (int *)iter_baton;
int sd;
char *buffer;
const char *cache_id = NULL;
const char *request = NULL;
*credentials = NULL;
if (svn_hash_gets(parameters, SVN_AUTH_PARAM_NON_INTERACTIVE))
{
return SVN_NO_ERROR;
}
*attempt = *attempt + 1;
SVN_ERR(find_running_gpg_agent(&sd, pool));
if (sd == -1)
return SVN_NO_ERROR;
buffer = apr_palloc(pool, BUFFER_SIZE);
if (!send_options(sd, buffer, BUFFER_SIZE, pool))
{
bye_gpg_agent(sd);
return SVN_NO_ERROR;
}
SVN_ERR(get_cache_id(&cache_id, realmstring, pool, pool));
request = apr_psprintf(pool, "CLEAR_PASSPHRASE %s\n", cache_id);
if (write(sd, request, strlen(request)) == -1)
{
bye_gpg_agent(sd);
return SVN_NO_ERROR;
}
if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE))
{
bye_gpg_agent(sd);
return SVN_NO_ERROR;
}
bye_gpg_agent(sd);
if (strncmp(buffer, "OK\n", 3) != 0)
return SVN_NO_ERROR;
if (*attempt < 4)
return svn_auth__simple_creds_cache_get(credentials, &iter_baton,
provider_baton, parameters,
realmstring,
password_get_gpg_agent,
SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
pool);
return SVN_NO_ERROR;
}
static svn_error_t *
simple_gpg_agent_save_creds(svn_boolean_t *saved,
void *credentials,
void *provider_baton,
apr_hash_t *parameters,
const char *realmstring,
apr_pool_t *pool)
{
return svn_auth__simple_creds_cache_set(saved, credentials,
provider_baton, parameters,
realmstring, password_set_gpg_agent,
SVN_AUTH__GPG_AGENT_PASSWORD_TYPE,
pool);
}
static const svn_auth_provider_t gpg_agent_simple_provider = {
SVN_AUTH_CRED_SIMPLE,
simple_gpg_agent_first_creds,
simple_gpg_agent_next_creds,
simple_gpg_agent_save_creds
};
void
svn_auth__get_gpg_agent_simple_provider(svn_auth_provider_object_t **provider,
apr_pool_t *pool)
{
svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po));
po->vtable = &gpg_agent_simple_provider;
*provider = po;
}
#endif
#endif