1251881Speter/* 2251881Speter * gpg_agent.c: GPG Agent provider for SVN_AUTH_CRED_* 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter/* ==================================================================== */ 25251881Speter 26251881Speter/* This auth provider stores a plaintext password in memory managed by 27251881Speter * a running gpg-agent. In contrast to other password store providers 28251881Speter * it does not save the password to disk. 29251881Speter * 30251881Speter * Prompting is performed by the gpg-agent using a "pinentry" program 31251881Speter * which needs to be installed separately. There are several pinentry 32251881Speter * implementations with different front-ends (e.g. qt, gtk, ncurses). 33251881Speter * 34251881Speter * The gpg-agent will let the password time out after a while, 35251881Speter * or immediately when it receives the SIGHUP signal. 36251881Speter * When the password has timed out it will automatically prompt the 37251881Speter * user for the password again. This is transparent to Subversion. 38251881Speter * 39251881Speter * SECURITY CONSIDERATIONS: 40251881Speter * 41251881Speter * Communication to the agent happens over a UNIX socket, which is located 42251881Speter * in a directory which only the user running Subversion can access. 43251881Speter * However, any program the user runs could access this socket and get 44251881Speter * the Subversion password if the program knows the "cache ID" Subversion 45251881Speter * uses for the password. 46251881Speter * The cache ID is very easy to obtain for programs running as the same user. 47251881Speter * Subversion uses the MD5 of the realmstring as cache ID, and these checksums 48251881Speter * are also used as filenames within ~/.subversion/auth/svn.simple. 49251881Speter * Unlike GNOME Keyring or KDE Wallet, the user is not prompted for 50251881Speter * permission if another program attempts to access the password. 51251881Speter * 52251881Speter * Therefore, while the gpg-agent is running and has the password cached, 53251881Speter * this provider is no more secure than a file storing the password in 54251881Speter * plaintext. 55251881Speter */ 56251881Speter 57251881Speter 58251881Speter/*** Includes. ***/ 59251881Speter 60251881Speter#ifndef WIN32 61251881Speter 62251881Speter#include <unistd.h> 63251881Speter 64251881Speter#include <sys/socket.h> 65251881Speter#include <sys/un.h> 66251881Speter 67251881Speter#include <apr_pools.h> 68251881Speter#include "svn_auth.h" 69251881Speter#include "svn_config.h" 70251881Speter#include "svn_error.h" 71251881Speter#include "svn_pools.h" 72251881Speter#include "svn_cmdline.h" 73251881Speter#include "svn_checksum.h" 74251881Speter#include "svn_string.h" 75251881Speter 76251881Speter#include "private/svn_auth_private.h" 77251881Speter 78251881Speter#include "svn_private_config.h" 79251881Speter 80251881Speter#ifdef SVN_HAVE_GPG_AGENT 81251881Speter 82251881Speter#define BUFFER_SIZE 1024 83251881Speter 84251881Speter/* Modify STR in-place such that blanks are escaped as required by the 85251881Speter * gpg-agent protocol. Return a pointer to STR. */ 86251881Speterstatic char * 87251881Speterescape_blanks(char *str) 88251881Speter{ 89251881Speter char *s = str; 90251881Speter 91251881Speter while (*s) 92251881Speter { 93251881Speter if (*s == ' ') 94251881Speter *s = '+'; 95251881Speter s++; 96251881Speter } 97251881Speter 98251881Speter return str; 99251881Speter} 100251881Speter 101251881Speter/* Attempt to read a gpg-agent response message from the socket SD into 102251881Speter * buffer BUF. Buf is assumed to be N bytes large. Return TRUE if a response 103251881Speter * message could be read that fits into the buffer. Else return FALSE. 104251881Speter * If a message could be read it will always be NUL-terminated and the 105251881Speter * trailing newline is retained. */ 106251881Speterstatic svn_boolean_t 107251881Speterreceive_from_gpg_agent(int sd, char *buf, size_t n) 108251881Speter{ 109251881Speter int i = 0; 110251881Speter size_t recvd; 111251881Speter char c; 112251881Speter 113251881Speter /* Clear existing buffer content before reading response. */ 114251881Speter if (n > 0) 115251881Speter *buf = '\0'; 116251881Speter 117251881Speter /* Require the message to fit into the buffer and be terminated 118251881Speter * with a newline. */ 119251881Speter while (i < n) 120251881Speter { 121251881Speter recvd = read(sd, &c, 1); 122251881Speter if (recvd == -1) 123251881Speter return FALSE; 124251881Speter buf[i] = c; 125251881Speter i++; 126251881Speter if (i < n && c == '\n') 127251881Speter { 128251881Speter buf[i] = '\0'; 129251881Speter return TRUE; 130251881Speter } 131251881Speter } 132251881Speter 133251881Speter return FALSE; 134251881Speter} 135251881Speter 136251881Speter/* Using socket SD, send the option OPTION with the specified VALUE 137251881Speter * to the gpg agent. Store the response in BUF, assumed to be N bytes 138251881Speter * in size, and evaluate the response. Return TRUE if the agent liked 139251881Speter * the smell of the option, if there is such a thing, and doesn't feel 140251881Speter * saturated by it. Else return FALSE. 141251881Speter * Do temporary allocations in scratch_pool. */ 142251881Speterstatic svn_boolean_t 143251881Spetersend_option(int sd, char *buf, size_t n, const char *option, const char *value, 144251881Speter apr_pool_t *scratch_pool) 145251881Speter{ 146251881Speter const char *request; 147251881Speter 148251881Speter request = apr_psprintf(scratch_pool, "OPTION %s=%s\n", option, value); 149251881Speter 150251881Speter if (write(sd, request, strlen(request)) == -1) 151251881Speter return FALSE; 152251881Speter 153251881Speter if (!receive_from_gpg_agent(sd, buf, n)) 154251881Speter return FALSE; 155251881Speter 156251881Speter return (strncmp(buf, "OK", 2) == 0); 157251881Speter} 158251881Speter 159253734Speter 160253734Speter/* Locate a running GPG Agent, and return an open file descriptor 161253734Speter * for communication with the agent in *NEW_SD. If no running agent 162253734Speter * can be found, set *NEW_SD to -1. */ 163251881Speterstatic svn_error_t * 164253734Speterfind_running_gpg_agent(int *new_sd, apr_pool_t *pool) 165251881Speter{ 166253734Speter char *buffer; 167251881Speter char *gpg_agent_info = NULL; 168253734Speter const char *socket_name = NULL; 169253734Speter const char *request = NULL; 170251881Speter const char *p = NULL; 171251881Speter char *ep = NULL; 172253734Speter int sd; 173251881Speter 174253734Speter *new_sd = -1; 175251881Speter 176251881Speter gpg_agent_info = getenv("GPG_AGENT_INFO"); 177251881Speter if (gpg_agent_info != NULL) 178251881Speter { 179253734Speter apr_array_header_t *socket_details; 180253734Speter 181251881Speter socket_details = svn_cstring_split(gpg_agent_info, ":", TRUE, 182251881Speter pool); 183251881Speter socket_name = APR_ARRAY_IDX(socket_details, 0, const char *); 184251881Speter } 185251881Speter else 186251881Speter return SVN_NO_ERROR; 187251881Speter 188251881Speter if (socket_name != NULL) 189251881Speter { 190253734Speter struct sockaddr_un addr; 191253734Speter 192251881Speter addr.sun_family = AF_UNIX; 193251881Speter strncpy(addr.sun_path, socket_name, sizeof(addr.sun_path) - 1); 194251881Speter addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; 195251881Speter 196251881Speter sd = socket(AF_UNIX, SOCK_STREAM, 0); 197251881Speter if (sd == -1) 198251881Speter return SVN_NO_ERROR; 199251881Speter 200251881Speter if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1) 201251881Speter { 202251881Speter close(sd); 203251881Speter return SVN_NO_ERROR; 204251881Speter } 205251881Speter } 206251881Speter else 207251881Speter return SVN_NO_ERROR; 208251881Speter 209251881Speter /* Receive the connection status from the gpg-agent daemon. */ 210251881Speter buffer = apr_palloc(pool, BUFFER_SIZE); 211251881Speter if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE)) 212251881Speter { 213251881Speter close(sd); 214251881Speter return SVN_NO_ERROR; 215251881Speter } 216251881Speter 217251881Speter if (strncmp(buffer, "OK", 2) != 0) 218251881Speter { 219251881Speter close(sd); 220251881Speter return SVN_NO_ERROR; 221251881Speter } 222251881Speter 223251881Speter /* The GPG-Agent documentation says: 224251881Speter * "Clients should deny to access an agent with a socket name which does 225251881Speter * not match its own configuration". */ 226251881Speter request = "GETINFO socket_name\n"; 227251881Speter if (write(sd, request, strlen(request)) == -1) 228251881Speter { 229251881Speter close(sd); 230251881Speter return SVN_NO_ERROR; 231251881Speter } 232251881Speter if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE)) 233251881Speter { 234251881Speter close(sd); 235251881Speter return SVN_NO_ERROR; 236251881Speter } 237251881Speter if (strncmp(buffer, "D", 1) == 0) 238251881Speter p = &buffer[2]; 239251881Speter if (!p) 240251881Speter { 241251881Speter close(sd); 242251881Speter return SVN_NO_ERROR; 243251881Speter } 244251881Speter ep = strchr(p, '\n'); 245251881Speter if (ep != NULL) 246251881Speter *ep = '\0'; 247251881Speter if (strcmp(socket_name, p) != 0) 248251881Speter { 249251881Speter close(sd); 250251881Speter return SVN_NO_ERROR; 251251881Speter } 252251881Speter /* The agent will terminate its response with "OK". */ 253251881Speter if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE)) 254251881Speter { 255251881Speter close(sd); 256251881Speter return SVN_NO_ERROR; 257251881Speter } 258251881Speter if (strncmp(buffer, "OK", 2) != 0) 259251881Speter { 260251881Speter close(sd); 261251881Speter return SVN_NO_ERROR; 262251881Speter } 263251881Speter 264253734Speter *new_sd = sd; 265253734Speter return SVN_NO_ERROR; 266253734Speter} 267253734Speter 268253734Speter/* Implementation of svn_auth__password_get_t that retrieves the password 269253734Speter from gpg-agent */ 270253734Speterstatic svn_error_t * 271253734Speterpassword_get_gpg_agent(svn_boolean_t *done, 272253734Speter const char **password, 273253734Speter apr_hash_t *creds, 274253734Speter const char *realmstring, 275253734Speter const char *username, 276253734Speter apr_hash_t *parameters, 277253734Speter svn_boolean_t non_interactive, 278253734Speter apr_pool_t *pool) 279253734Speter{ 280253734Speter int sd; 281253734Speter const char *p = NULL; 282253734Speter char *ep = NULL; 283253734Speter char *buffer; 284253734Speter const char *request = NULL; 285253734Speter const char *cache_id = NULL; 286253734Speter const char *tty_name; 287253734Speter const char *tty_type; 288253734Speter const char *lc_ctype; 289253734Speter const char *display; 290253734Speter svn_checksum_t *digest = NULL; 291253734Speter char *password_prompt; 292253734Speter char *realm_prompt; 293253734Speter 294253734Speter *done = FALSE; 295253734Speter 296253734Speter SVN_ERR(find_running_gpg_agent(&sd, pool)); 297253734Speter if (sd == -1) 298253734Speter return SVN_NO_ERROR; 299253734Speter 300253734Speter buffer = apr_palloc(pool, BUFFER_SIZE); 301253734Speter 302251881Speter /* Send TTY_NAME to the gpg-agent daemon. */ 303251881Speter tty_name = getenv("GPG_TTY"); 304251881Speter if (tty_name != NULL) 305251881Speter { 306251881Speter if (!send_option(sd, buffer, BUFFER_SIZE, "ttyname", tty_name, pool)) 307251881Speter { 308251881Speter close(sd); 309251881Speter return SVN_NO_ERROR; 310251881Speter } 311251881Speter } 312251881Speter 313251881Speter /* Send TTY_TYPE to the gpg-agent daemon. */ 314251881Speter tty_type = getenv("TERM"); 315251881Speter if (tty_type != NULL) 316251881Speter { 317251881Speter if (!send_option(sd, buffer, BUFFER_SIZE, "ttytype", tty_type, pool)) 318251881Speter { 319251881Speter close(sd); 320251881Speter return SVN_NO_ERROR; 321251881Speter } 322251881Speter } 323251881Speter 324251881Speter /* Compute LC_CTYPE. */ 325251881Speter lc_ctype = getenv("LC_ALL"); 326251881Speter if (lc_ctype == NULL) 327251881Speter lc_ctype = getenv("LC_CTYPE"); 328251881Speter if (lc_ctype == NULL) 329251881Speter lc_ctype = getenv("LANG"); 330251881Speter 331251881Speter /* Send LC_CTYPE to the gpg-agent daemon. */ 332251881Speter if (lc_ctype != NULL) 333251881Speter { 334251881Speter if (!send_option(sd, buffer, BUFFER_SIZE, "lc-ctype", lc_ctype, pool)) 335251881Speter { 336251881Speter close(sd); 337251881Speter return SVN_NO_ERROR; 338251881Speter } 339251881Speter } 340251881Speter 341251881Speter /* Send DISPLAY to the gpg-agent daemon. */ 342251881Speter display = getenv("DISPLAY"); 343251881Speter if (display != NULL) 344251881Speter { 345251881Speter if (!send_option(sd, buffer, BUFFER_SIZE, "display", display, pool)) 346251881Speter { 347251881Speter close(sd); 348251881Speter return SVN_NO_ERROR; 349251881Speter } 350251881Speter } 351251881Speter 352251881Speter /* Create the CACHE_ID which will be generated based on REALMSTRING similar 353251881Speter to other password caching mechanisms. */ 354251881Speter SVN_ERR(svn_checksum(&digest, svn_checksum_md5, realmstring, 355251881Speter strlen(realmstring), pool)); 356251881Speter cache_id = svn_checksum_to_cstring(digest, pool); 357251881Speter 358251881Speter password_prompt = apr_psprintf(pool, _("Password for '%s': "), username); 359251881Speter realm_prompt = apr_psprintf(pool, _("Enter your Subversion password for %s"), 360251881Speter realmstring); 361251881Speter request = apr_psprintf(pool, 362251881Speter "GET_PASSPHRASE --data %s--repeat=1 " 363251881Speter "%s X %s %s\n", 364251881Speter non_interactive ? "--no-ask " : "", 365251881Speter cache_id, 366251881Speter escape_blanks(password_prompt), 367251881Speter escape_blanks(realm_prompt)); 368251881Speter 369251881Speter if (write(sd, request, strlen(request)) == -1) 370251881Speter { 371251881Speter close(sd); 372251881Speter return SVN_NO_ERROR; 373251881Speter } 374251881Speter if (!receive_from_gpg_agent(sd, buffer, BUFFER_SIZE)) 375251881Speter { 376251881Speter close(sd); 377251881Speter return SVN_NO_ERROR; 378251881Speter } 379251881Speter 380251881Speter close(sd); 381251881Speter 382251881Speter if (strncmp(buffer, "ERR", 3) == 0) 383251881Speter return SVN_NO_ERROR; 384251881Speter 385251881Speter p = NULL; 386251881Speter if (strncmp(buffer, "D", 1) == 0) 387251881Speter p = &buffer[2]; 388251881Speter 389251881Speter if (!p) 390251881Speter return SVN_NO_ERROR; 391251881Speter 392251881Speter ep = strchr(p, '\n'); 393251881Speter if (ep != NULL) 394251881Speter *ep = '\0'; 395251881Speter 396251881Speter *password = p; 397251881Speter 398251881Speter *done = TRUE; 399251881Speter return SVN_NO_ERROR; 400251881Speter} 401251881Speter 402251881Speter 403251881Speter/* Implementation of svn_auth__password_set_t that would store the 404251881Speter password in GPG Agent if that's how this particular integration 405251881Speter worked. But it isn't. GPG Agent stores the password provided by 406251881Speter the user via the pinentry program immediately upon its provision 407253734Speter (and regardless of its accuracy as passwords go), so we just need 408253734Speter to check if a running GPG Agent exists. */ 409251881Speterstatic svn_error_t * 410251881Speterpassword_set_gpg_agent(svn_boolean_t *done, 411251881Speter apr_hash_t *creds, 412251881Speter const char *realmstring, 413251881Speter const char *username, 414251881Speter const char *password, 415251881Speter apr_hash_t *parameters, 416251881Speter svn_boolean_t non_interactive, 417251881Speter apr_pool_t *pool) 418251881Speter{ 419253734Speter int sd; 420253734Speter 421253734Speter *done = FALSE; 422253734Speter 423253734Speter SVN_ERR(find_running_gpg_agent(&sd, pool)); 424253734Speter if (sd == -1) 425253734Speter return SVN_NO_ERROR; 426253734Speter 427253734Speter close(sd); 428251881Speter *done = TRUE; 429251881Speter 430251881Speter return SVN_NO_ERROR; 431251881Speter} 432251881Speter 433251881Speter 434251881Speter/* An implementation of svn_auth_provider_t::first_credentials() */ 435251881Speterstatic svn_error_t * 436251881Spetersimple_gpg_agent_first_creds(void **credentials, 437251881Speter void **iter_baton, 438251881Speter void *provider_baton, 439251881Speter apr_hash_t *parameters, 440251881Speter const char *realmstring, 441251881Speter apr_pool_t *pool) 442251881Speter{ 443251881Speter return svn_auth__simple_creds_cache_get(credentials, iter_baton, 444251881Speter provider_baton, parameters, 445251881Speter realmstring, password_get_gpg_agent, 446251881Speter SVN_AUTH__GPG_AGENT_PASSWORD_TYPE, 447251881Speter pool); 448251881Speter} 449251881Speter 450251881Speter 451251881Speter/* An implementation of svn_auth_provider_t::save_credentials() */ 452251881Speterstatic svn_error_t * 453251881Spetersimple_gpg_agent_save_creds(svn_boolean_t *saved, 454251881Speter void *credentials, 455251881Speter void *provider_baton, 456251881Speter apr_hash_t *parameters, 457251881Speter const char *realmstring, 458251881Speter apr_pool_t *pool) 459251881Speter{ 460251881Speter return svn_auth__simple_creds_cache_set(saved, credentials, 461251881Speter provider_baton, parameters, 462251881Speter realmstring, password_set_gpg_agent, 463251881Speter SVN_AUTH__GPG_AGENT_PASSWORD_TYPE, 464251881Speter pool); 465251881Speter} 466251881Speter 467251881Speter 468251881Speterstatic const svn_auth_provider_t gpg_agent_simple_provider = { 469251881Speter SVN_AUTH_CRED_SIMPLE, 470251881Speter simple_gpg_agent_first_creds, 471251881Speter NULL, 472251881Speter simple_gpg_agent_save_creds 473251881Speter}; 474251881Speter 475251881Speter 476251881Speter/* Public API */ 477251881Spetervoid 478251881Spetersvn_auth_get_gpg_agent_simple_provider(svn_auth_provider_object_t **provider, 479251881Speter apr_pool_t *pool) 480251881Speter{ 481251881Speter svn_auth_provider_object_t *po = apr_pcalloc(pool, sizeof(*po)); 482251881Speter 483251881Speter po->vtable = &gpg_agent_simple_provider; 484251881Speter *provider = po; 485251881Speter} 486251881Speter 487251881Speter#endif /* SVN_HAVE_GPG_AGENT */ 488251881Speter#endif /* !WIN32 */ 489