view release on metacpan or search on metacpan
src/subversion/subversion/libsvn_subr/ctype.c
src/subversion/subversion/libsvn_subr/date.c
src/subversion/subversion/libsvn_subr/debug.c
src/subversion/subversion/libsvn_subr/deprecated.c
src/subversion/subversion/libsvn_subr/dirent_uri.c
src/subversion/subversion/libsvn_subr/dirent_uri.h
src/subversion/subversion/libsvn_subr/dso.c
src/subversion/subversion/libsvn_subr/eol.c
src/subversion/subversion/libsvn_subr/error.c
src/subversion/subversion/libsvn_subr/genctype.py
src/subversion/subversion/libsvn_subr/gpg_agent.c
src/subversion/subversion/libsvn_subr/hash.c
src/subversion/subversion/libsvn_subr/internal_statements.h
src/subversion/subversion/libsvn_subr/internal_statements.sql
src/subversion/subversion/libsvn_subr/io.c
src/subversion/subversion/libsvn_subr/iter.c
src/subversion/subversion/libsvn_subr/lock.c
src/subversion/subversion/libsvn_subr/log.c
src/subversion/subversion/libsvn_subr/macos_keychain.c
src/subversion/subversion/libsvn_subr/magic.c
src/subversion/subversion/libsvn_subr/md5.c
src/subversion/CHANGES view on Meta::CPAN
User-visible changes:
- Client-side bugfixes:
* checkout/update: fix file externals failing to follow history and
subsequently silently failing (issue #4185)
* patch: don't skip targets in valid --git difs (r1592014, r1592034)
* diff: make property output in diffs stable (r1589360)
* diff: fix diff of local copied directory with props (r1619380, r1619393)
* diff: fix changelist filter for repos-WC and WC-WC (r1621978, r1621981)
* remove broken conflict resolver menu options that always error out
(r1620332)
* improve gpg-agent support (r1600331, r1600348, 1600368, r1600563,
r1600781)
* fix crash in eclipse IDE with GNOME Keyring (issue #3498)
* fix externals shadowing a versioned directory (issue #4085)
* fix problems working on unix file systems that don't support
permissions (r1612225)
* upgrade: keep external registrations (issue #4519)
* cleanup: iprove performance of recorded timestamp fixups (r1633126)
* translation updates for German
- Server-side bugfixes:
src/subversion/CHANGES view on Meta::CPAN
* diff: avoid temporary files when calling external diff (issue #4382)
* upgrade: fix notification of 1.7.x working copies (r1493703, r1494171)
* fix crash during tree conflict resolution (issue #4388)
* interactive file merge: add two additional choices (r1491816, r1494089)
* diff: use local style paths in error messages (r1500680)
* resolve: improve the interactive conflict resolution menu (r1491739 et al)
* switch: use local style path in error message (r1500074)
* ra_serf: improve error output when receiving invalid XML (r1498851)
* svn cleanup: explain what the command does in help output (r1497310)
* blame: error on -r M:N where M>N unless server supports (r1498449 et al)
* gpg-agent auth: don't try to use agent when unavailable (r1500762 et al)
* gpg-agent auth: don't require GPG_TTY or TERM env vars (r1500801)
* update: fix some tree conflicts not triggering resolver (r1491868 et al)
* commit: remove stale entries from wc lock table when deleting (r1491756)
* merge: fix --record-only erroring out on renamed path (issue #4387)
* svnmucc: fix 'make install' symlink to work when DESTDIR is set (r1501072)
* wc: fix crash when target is symlink to a working copy root (issue #4383)
* ra_serf: change "internal malfunction" errors to normal errors (r1502577)
* ra_serf: handle proxies not supporting chunked requests (r1502401 et al)
- Server-side bugfixes:
* fsfs: resolve endless loop problem when repos/db/uuid has \r\n (r1492145)
src/subversion/CHANGES view on Meta::CPAN
- General:
* require serf as client-side http library (neon support removed) (r1349694)
* deprecate the Berkeley DB FS backend (libsvn_fs_base) (r1464985 et al)
- Major new features:
* working copy records moves as first-class operation (issue #3631, #4232)
* merge uses reintegrate mode automatically when needed (r1369896 et al)
* FSFS: Packing of revision property shards (issue #3944)
* support inheritable properties (r1395109)
* repository can suggest config for autoprops and ignores (r1401908)
* support gpg-agent for password caching (r1151069)
* authz rules can be stored inside the repository (r1424780)
- Minor new features and improvements (client-side):
* doubled svn:// protocol throughput (r1325899)
* optimize file/dir truename checks on Windows (r1435527)
* new 'commit --include-externals' option (related to issues #1167, #3563)
* new --include-externals option for 'svn list' (issue #4225)
* remove extraneous externals output from 'svn status -q' (issue #1935)
* reject some attempts to merge between unrelated branches (r1215273)
* new --ignore-properties option for 'svn diff' (r1239553, -617)
src/subversion/Makefile.in view on Meta::CPAN
LIBS = @LIBS@
prefix = @prefix@
exec_prefix = @exec_prefix@
libdir = @libdir@
fsmod_libdir = @libdir@
ramod_libdir = @libdir@
bdb_libdir = @libdir@
gnome_keyring_libdir = @libdir@
gpg_agent_libdir = @libdir@
kwallet_libdir = @libdir@
serf_libdir = @libdir@
bindir = @bindir@
includedir = @includedir@
mandir = @mandir@
srcdir = @srcdir@
canonicalized_srcdir = @canonicalized_srcdir@
datadir = @datadir@
datarootdir = @datarootdir@
localedir = @localedir@
src/subversion/build-outputs.mk view on Meta::CPAN
subversion/libsvn_ra_svn/libsvn_ra_svn-1.la: $(libsvn_ra_svn_DEPS)
cd subversion/libsvn_ra_svn && $(LINK_LIB) $(libsvn_ra_svn_LDFLAGS) -o libsvn_ra_svn-1.la $(LT_NO_UNDEFINED) $(libsvn_ra_svn_OBJECTS) ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/libsvn_subr-1.la $(SVN_APRUTIL_LIBS) $...
libsvn_repos_PATH = subversion/libsvn_repos
libsvn_repos_DEPS = subversion/libsvn_repos/authz.lo subversion/libsvn_repos/commit.lo subversion/libsvn_repos/delta.lo subversion/libsvn_repos/deprecated.lo subversion/libsvn_repos/dump.lo subversion/libsvn_repos/fs-wrap.lo subversion/libsvn_repos/...
libsvn_repos_OBJECTS = authz.lo commit.lo delta.lo deprecated.lo dump.lo fs-wrap.lo hooks.lo load-fs-vtable.lo load.lo log.lo node_tree.lo notify.lo replay.lo reporter.lo repos.lo rev_hunt.lo
subversion/libsvn_repos/libsvn_repos-1.la: $(libsvn_repos_DEPS)
cd subversion/libsvn_repos && $(LINK_LIB) $(libsvn_repos_LDFLAGS) -o libsvn_repos-1.la $(LT_NO_UNDEFINED) $(libsvn_repos_OBJECTS) ../../subversion/libsvn_fs/libsvn_fs-1.la ../../subversion/libsvn_delta/libsvn_delta-1.la ../../subversion/libsvn_subr/...
libsvn_subr_PATH = subversion/libsvn_subr
libsvn_subr_DEPS = subversion/libsvn_subr/adler32.lo subversion/libsvn_subr/atomic.lo subversion/libsvn_subr/auth.lo subversion/libsvn_subr/base64.lo subversion/libsvn_subr/cache-inprocess.lo subversion/libsvn_subr/cache-membuffer.lo subversion/libs...
libsvn_subr_OBJECTS = adler32.lo atomic.lo auth.lo base64.lo cache-inprocess.lo cache-membuffer.lo cache-memcache.lo cache.lo cache_config.lo checksum.lo cmdline.lo compat.lo config.lo config_auth.lo config_file.lo config_win.lo crypto.lo ctype.lo da...
subversion/libsvn_subr/libsvn_subr-1.la: $(libsvn_subr_DEPS)
cd subversion/libsvn_subr && $(LINK_LIB) $(libsvn_subr_LDFLAGS) -o libsvn_subr-1.la $(LT_NO_UNDEFINED) $(libsvn_subr_OBJECTS) $(SVN_APRUTIL_LIBS) $(SVN_APR_LIBS) $(SVN_XML_LIBS) $(SVN_ZLIB_LIBS) $(SVN_APR_MEMCACHE_LIBS) $(SVN_SQLITE_LIBS) $(SVN_MAGI...
libsvn_swig_perl_PATH = subversion/bindings/swig/perl/libsvn_swig_perl
libsvn_swig_perl_DEPS = subversion/bindings/swig/perl/libsvn_swig_perl/swigutil_pl.lo subversion/libsvn_delta/libsvn_delta-1.la subversion/libsvn_subr/libsvn_subr-1.la
libsvn_swig_perl_OBJECTS = swigutil_pl.lo
subversion/bindings/swig/perl/libsvn_swig_perl/libsvn_swig_perl-1.la: $(libsvn_swig_perl_DEPS)
cd subversion/bindings/swig/perl/libsvn_swig_perl && $(LINK_LIB) $(libsvn_swig_perl_LDFLAGS) -o libsvn_swig_perl-1.la $(LT_NO_UNDEFINED) $(libsvn_swig_perl_OBJECTS) ../../../../../subversion/libsvn_delta/libsvn_delta-1.la ../../../../../subversion/l...
libsvn_swig_py_PATH = subversion/bindings/swig/python/libsvn_swig_py
src/subversion/build-outputs.mk view on Meta::CPAN
subversion/libsvn_subr/deprecated.lo: subversion/libsvn_subr/deprecated.c subversion/include/private/svn_debug.h subversion/include/private/svn_mergeinfo_private.h subversion/include/private/svn_opt_private.h subversion/include/private/svn_subr_priva...
subversion/libsvn_subr/dirent_uri.lo: subversion/libsvn_subr/dirent_uri.c subversion/include/private/svn_cert.h subversion/include/private/svn_debug.h subversion/include/private/svn_fspath.h subversion/include/svn_ctype.h subversion/include/svn_diren...
subversion/libsvn_subr/dso.lo: subversion/libsvn_subr/dso.c subversion/include/private/svn_debug.h subversion/include/private/svn_mutex.h subversion/include/svn_checksum.h subversion/include/svn_dso.h subversion/include/svn_error.h subversion/include...
subversion/libsvn_subr/eol.lo: subversion/libsvn_subr/eol.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_eol_private.h subversion/include/svn_checksum.h subversion/include/svn_error...
subversion/libsvn_subr/error.lo: subversion/libsvn_subr/error.c subversion/include/private/svn_debug.h subversion/include/private/svn_error_private.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdline.h sub...
subversion/libsvn_subr/gpg_agent.lo: subversion/libsvn_subr/gpg_agent.c subversion/include/private/svn_auth_private.h subversion/include/private/svn_debug.h subversion/include/svn_auth.h subversion/include/svn_checksum.h subversion/include/svn_cmdlin...
subversion/libsvn_subr/hash.lo: subversion/libsvn_subr/hash.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_subr_private.h subversion/include/svn_checksum.h subversion/include/svn_er...
subversion/libsvn_subr/io.lo: subversion/libsvn_subr/io.c subversion/include/private/svn_atomic.h subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/private/svn_io_private.h subversion/include/svn_ch...
subversion/libsvn_subr/iter.lo: subversion/libsvn_subr/iter.c subversion/include/private/svn_debug.h subversion/include/private/svn_dep_compat.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_iter.h subvers...
subversion/libsvn_subr/lock.lo: subversion/libsvn_subr/lock.c subversion/include/private/svn_debug.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion/include/svn_types.h
subversion/libsvn_subr/log.lo: subversion/libsvn_subr/log.c subversion/include/private/svn_debug.h subversion/include/private/svn_log.h subversion/include/svn_dirent_uri.h subversion/include/svn_error.h subversion/include/svn_error_codes.h subversion...
src/subversion/build/generator/extractor.py view on Meta::CPAN
# Not available on Windows
'svn_auth_get_keychain_simple_provider',
'svn_auth_get_keychain_ssl_client_cert_pw_provider',
'svn_auth_get_gnome_keyring_simple_provider',
'svn_auth_get_gnome_keyring_ssl_client_cert_pw_provider',
'svn_auth_get_kwallet_simple_provider',
'svn_auth_get_kwallet_ssl_client_cert_pw_provider',
'svn_auth_gnome_keyring_version',
'svn_auth_kwallet_version',
'svn_auth_get_gpg_agent_simple_provider',
'svn_auth_gpg_agent_version',
# -DSVN_FS_INFO:
'svn_repos_capabilities',
'svn_repos_info_format',
'svn_fs_info_format',
'svn_fs_info_config_files',
'svn_fs_info',
'svn_fs_info_dup',
]
if __name__ == '__main__':
src/subversion/configure view on Meta::CPAN
enable_experimental_libtool
enable_all_static
enable_local_library_preloading
with_trang
with_doxygen
with_expat
with_berkeley_db
enable_bdb6
with_sasl
enable_keychain
with_gpg_agent
with_gnome_keyring
enable_ev2_impl
enable_nls
with_libmagic
with_kwallet
enable_plaintext_password_storage
with_openssl
enable_debug
enable_optimize
enable_disallowing_of_undefined_references
src/subversion/configure view on Meta::CPAN
--with-expat=INCLUDES:LIB_SEARCH_DIRS:LIBS
Specify location of Expat
--with-berkeley-db[=HEADER:INCLUDES:LIB_SEARCH_DIRS:LIBS]
The Subversion Berkeley DB based filesystem library
requires Berkeley DB $db_version or $db_alt_version.
If you specify `--without-berkeley-db', that library
will not be built. If you omit the argument of this
option completely, the configure script will use
Berkeley DB used by APR-UTIL.
--with-sasl=PATH Compile with libsasl2 in PATH
--without-gpg-agent Disable support for GPG-Agent
--with-gnome-keyring Enable use of GNOME Keyring for auth credentials
(enabled by default if found)
--with-libmagic=PREFIX libmagic filetype detection library
--with-kwallet[=PATH] Enable use of KWallet (KDE 4) for auth credentials
--with-openssl This option does NOT affect the Subversion build
process in any way. It tells an integrated Serf HTTP
client library build process where to locate the
OpenSSL library when (and only when) building Serf
as an integrated part of the Subversion build
process. When linking to a previously installed
src/subversion/configure view on Meta::CPAN
CPPFLAGS="$old_CPPFLAGS"
LIBS="$old_LIBS"
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
fi
# Check whether --with-gpg_agent was given.
if test "${with_gpg_agent+set}" = set; then :
withval=$with_gpg_agent;
else
with_gpg_agent=yes
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether to support GPG-Agent" >&5
$as_echo_n "checking whether to support GPG-Agent... " >&6; }
if test "$with_gpg_agent" = "yes"; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
$as_echo "#define SVN_HAVE_GPG_AGENT 1" >>confdefs.h
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
fi
src/subversion/configure.ac view on Meta::CPAN
fi
CPPFLAGS="$old_CPPFLAGS"
LIBS="$old_LIBS"
else
AC_MSG_RESULT([no])
fi
fi
dnl GPG Agent -------------------
AC_ARG_WITH(gpg_agent,
AS_HELP_STRING([--without-gpg-agent],
[Disable support for GPG-Agent]),
[], [with_gpg_agent=yes])
AC_MSG_CHECKING([whether to support GPG-Agent])
if test "$with_gpg_agent" = "yes"; then
AC_MSG_RESULT([yes])
AC_DEFINE([SVN_HAVE_GPG_AGENT], [1],
[Is GPG Agent support enabled?])
else
AC_MSG_RESULT([no])
fi
AC_SUBST(SVN_HAVE_GPG_AGENT)
dnl GNOME Keyring -------------------
src/subversion/subversion/bindings/swig/core.i view on Meta::CPAN
%ignore svn_auth_get_keychain_simple_provider;
%ignore svn_auth_get_keychain_ssl_client_cert_pw_provider;
%ignore svn_auth_get_windows_simple_provider;
%ignore svn_auth_get_windows_ssl_server_trust_provider;
%ignore svn_auth_gnome_keyring_version;
%ignore svn_auth_get_gnome_keyring_simple_provider;
%ignore svn_auth_get_gnome_keyring_ssl_client_cert_pw_provider;
%ignore svn_auth_kwallet_version;
%ignore svn_auth_get_kwallet_simple_provider;
%ignore svn_auth_get_kwallet_ssl_client_cert_pw_provider;
%ignore svn_auth_get_gpg_agent_simple_provider;
/* bad pool convention */
%ignore svn_opt_print_generic_help;
%ignore svn_opt_args_to_target_array;
/* svn_cmdline.h */
%ignore svn_cmdline_auth_plaintext_passphrase_prompt;
%ignore svn_cmdline_auth_plaintext_prompt;
%ignore svn_cmdline_auth_simple_prompt;
src/subversion/subversion/include/private/svn_auth_private.h view on Meta::CPAN
/* If you add a password type for a provider which stores
* passwords on disk in encrypted form, remember to update
* svn_auth__simple_save_creds_helper. Otherwise it will be
* assumed that your provider stores passwords in plaintext. */
#define SVN_AUTH__SIMPLE_PASSWORD_TYPE "simple"
#define SVN_AUTH__WINCRYPT_PASSWORD_TYPE "wincrypt"
#define SVN_AUTH__KEYCHAIN_PASSWORD_TYPE "keychain"
#define SVN_AUTH__KWALLET_PASSWORD_TYPE "kwallet"
#define SVN_AUTH__GNOME_KEYRING_PASSWORD_TYPE "gnome-keyring"
#define SVN_AUTH__GPG_AGENT_PASSWORD_TYPE "gpg-agent"
/* A function that stores in *PASSWORD (potentially after decrypting it)
the user's password. It might be obtained directly from CREDS, or
from an external store, using REALMSTRING and USERNAME as keys.
(The behavior is undefined if REALMSTRING or USERNAME are NULL.)
If NON_INTERACTIVE is set, the user must not be involved in the
retrieval process. Set *DONE to TRUE if a password was stored
in *PASSWORD, to FALSE otherwise. POOL is used for any necessary
allocation. */
typedef svn_error_t * (*svn_auth__password_get_t)
src/subversion/subversion/include/svn_auth.h view on Meta::CPAN
SVN_DEPRECATED
void
svn_auth_get_simple_provider(svn_auth_provider_object_t **provider,
apr_pool_t *pool);
/** Set @a *provider to an authentication provider of type @c
* svn_auth_provider_object_t, or return @c NULL if the provider is not
* available for the requested platform or the requested provider is unknown.
*
* Valid @a provider_name values are: "gnome_keyring", "keychain", "kwallet",
* "gpg_agent", and "windows".
*
* Valid @a provider_type values are: "simple", "ssl_client_cert_pw" and
* "ssl_server_trust".
*
* Allocate @a *provider in @a pool.
*
* What actually happens is we invoke the appropriate provider function to
* supply the @a provider, like so:
*
* svn_auth_get_<name>_<type>_provider(@a provider, @a pool);
src/subversion/subversion/include/svn_auth.h view on Meta::CPAN
* returned. Order of the platform-specific authentication providers is
* determined by the 'password-stores' configuration option which is retrieved
* from @a config. @a config can be NULL.
*
* Create and allocate @a *providers in @a pool.
*
* Default order of the platform-specific authentication providers:
* 1. gnome-keyring
* 2. kwallet
* 3. keychain
* 4. gpg-agent
* 5. windows-cryptoapi
*
* @since New in 1.6.
*/
svn_error_t *
svn_auth_get_platform_specific_client_providers(
apr_array_header_t **providers,
svn_config_t *config,
apr_pool_t *pool);
src/subversion/subversion/include/svn_auth.h view on Meta::CPAN
apr_pool_t *pool);
#endif /* (!DARWIN && !WIN32) || DOXYGEN */
#if !defined(WIN32) || defined(DOXYGEN)
/**
* Set @a *provider to an authentication provider of type @c
* svn_auth_cred_simple_t that gets/sets information from the user's
* ~/.subversion configuration directory.
*
* This is like svn_client_get_simple_provider(), except that the
* password is obtained from gpg_agent, which will keep it in
* a memory cache.
*
* Allocate @a *provider in @a pool.
*
* @since New in 1.8
* @note This function actually works only on systems with
* GNU Privacy Guard installed.
*/
void
svn_auth_get_gpg_agent_simple_provider
(svn_auth_provider_object_t **provider,
apr_pool_t *pool);
#endif /* !defined(WIN32) || defined(DOXYGEN) */
/** Set @a *provider to an authentication provider of type @c
* svn_auth_cred_username_t that gets/sets information from a user's
* ~/.subversion configuration directory. Allocate @a *provider in
* @a pool.
*
src/subversion/subversion/libsvn_subr/auth.c view on Meta::CPAN
= provider_function_symbol;
provider_function(provider, pool);
}
}
}
#endif
}
else
{
#if defined(SVN_HAVE_GPG_AGENT)
if (strcmp(provider_name, "gpg_agent") == 0 &&
strcmp(provider_type, "simple") == 0)
{
svn_auth_get_gpg_agent_simple_provider(provider, pool);
}
#endif
#ifdef SVN_HAVE_KEYCHAIN_SERVICES
if (strcmp(provider_name, "keychain") == 0 &&
strcmp(provider_type, "simple") == 0)
{
svn_auth_get_keychain_simple_provider(provider, pool);
}
else if (strcmp(provider_name, "keychain") == 0 &&
strcmp(provider_type, "ssl_client_cert_pw") == 0)
src/subversion/subversion/libsvn_subr/auth.c view on Meta::CPAN
{
svn_auth_provider_object_t *provider;
const char *password_stores_config_option;
apr_array_header_t *password_stores;
int i;
#define SVN__MAYBE_ADD_PROVIDER(list, p) \
{ if (p) APR_ARRAY_PUSH(list, svn_auth_provider_object_t *) = p; }
#define SVN__DEFAULT_AUTH_PROVIDER_LIST \
"gnome-keyring,kwallet,keychain,gpg-agent,windows-cryptoapi"
*providers = apr_array_make(pool, 12, sizeof(svn_auth_provider_object_t *));
/* Fetch the configured list of password stores, and split them into
an array. */
svn_config_get(config,
&password_stores_config_option,
SVN_CONFIG_SECTION_AUTH,
SVN_CONFIG_OPTION_PASSWORD_STORES,
SVN__DEFAULT_AUTH_PROVIDER_LIST);
src/subversion/subversion/libsvn_subr/auth.c view on Meta::CPAN
pool));
SVN__MAYBE_ADD_PROVIDER(*providers, provider);
SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
"gnome_keyring",
"ssl_client_cert_pw",
pool));
SVN__MAYBE_ADD_PROVIDER(*providers, provider);
}
/* GPG-AGENT */
else if (apr_strnatcmp(password_store, "gpg-agent") == 0)
{
SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
"gpg_agent",
"simple",
pool));
SVN__MAYBE_ADD_PROVIDER(*providers, provider);
}
/* KWallet */
else if (apr_strnatcmp(password_store, "kwallet") == 0)
{
SVN_ERR(svn_auth_get_platform_specific_provider(&provider,
"kwallet",
"simple",
src/subversion/subversion/libsvn_subr/config_file.c view on Meta::CPAN
"### how to use this file." NL
"" NL
"### Section for authentication and authorization customizations." NL
"[auth]" NL
"### Set password stores used by Subversion. They should be" NL
"### delimited by spaces or commas. The order of values determines" NL
"### the order in which password stores are used." NL
"### Valid password stores:" NL
"### gnome-keyring (Unix-like systems)" NL
"### kwallet (Unix-like systems)" NL
"### gpg-agent (Unix-like systems)" NL
"### keychain (Mac OS X)" NL
"### windows-cryptoapi (Windows)" NL
#ifdef SVN_HAVE_KEYCHAIN_SERVICES
"# password-stores = keychain" NL
#elif defined(WIN32) && !defined(__MINGW32__)
"# password-stores = windows-cryptoapi" NL
#else
"# password-stores = gpg-agent,gnome-keyring,kwallet" NL
#endif
"### To disable all password stores, use an empty list:" NL
"# password-stores =" NL
#ifdef SVN_HAVE_KWALLET
"###" NL
"### Set KWallet wallet used by Subversion. If empty or unset," NL
"### then the default network wallet will be used." NL
"# kwallet-wallet =" NL
"###" NL
"### Include PID (Process ID) in Subversion application name when" NL
src/subversion/subversion/libsvn_subr/gpg_agent.c view on Meta::CPAN
/*
* gpg_agent.c: GPG Agent provider for SVN_AUTH_CRED_*
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
src/subversion/subversion/libsvn_subr/gpg_agent.c view on Meta::CPAN
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*/
/* ==================================================================== */
/* This auth provider stores a plaintext password in memory managed by
* a running gpg-agent. In contrast to other password store providers
* it does not save the password to disk.
*
* Prompting is performed by the gpg-agent using a "pinentry" program
* which needs to be installed separately. There are several pinentry
* implementations with different front-ends (e.g. qt, gtk, ncurses).
*
* The gpg-agent will let the password time out after a while,
* or immediately when it receives the SIGHUP signal.
* When the password has timed out it will automatically prompt the
* user for the password again. This is transparent to Subversion.
*
* SECURITY CONSIDERATIONS:
*
* Communication to the agent happens over a UNIX socket, which is located
* in a directory which only the user running Subversion can access.
* However, any program the user runs could access this socket and get
* the Subversion password if the program knows the "cache ID" Subversion
* uses for the password.
* The cache ID is very easy to obtain for programs running as the same user.
* Subversion uses the MD5 of the realmstring as cache ID, and these checksums
* are also used as filenames within ~/.subversion/auth/svn.simple.
* Unlike GNOME Keyring or KDE Wallet, the user is not prompted for
* permission if another program attempts to access the password.
*
* Therefore, while the gpg-agent is running and has the password cached,
* this provider is no more secure than a file storing the password in
* plaintext.
*/
/*** Includes. ***/
#ifndef WIN32
#include <unistd.h>
src/subversion/subversion/libsvn_subr/gpg_agent.c view on Meta::CPAN
#include "svn_user.h"
#include "svn_dirent_uri.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"
/* Modify STR in-place such that blanks are escaped as required by the
* gpg-agent protocol. Return a pointer to STR. */
static char *
escape_blanks(char *str)
{
char *s = str;
while (*s)
{
if (*s == ' ')
*s = '+';
s++;
src/subversion/subversion/libsvn_subr/gpg_agent.c view on Meta::CPAN
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;
}
/* Attempt to read a gpg-agent response message from the socket SD into
* buffer BUF. Buf is assumed to be N bytes large. Return TRUE if a response
* message could be read that fits into the buffer. Else return FALSE.
* If a message could be read it will always be NUL-terminated and the
* trailing newline is retained. */
static svn_boolean_t
receive_from_gpg_agent(int sd, char *buf, size_t n)
{
int i = 0;
size_t recvd;
char c;
/* Clear existing buffer content before reading response. */
if (n > 0)
*buf = '\0';
/* Require the message to fit into the buffer and be terminated
src/subversion/subversion/libsvn_subr/gpg_agent.c view on Meta::CPAN
{
buf[i] = '\0';
return TRUE;
}
}
return FALSE;
}
/* Using socket SD, send the option OPTION with the specified VALUE
* to the gpg agent. Store the response in BUF, assumed to be N bytes
* in size, and evaluate the response. Return TRUE if the agent liked
* the smell of the option, if there is such a thing, and doesn't feel
* saturated by it. Else return FALSE.
* Do temporary allocations in scratch_pool. */
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);
}
/* Send the BYE command and disconnect from the gpg-agent. Doing this avoids
* gpg-agent emitting a "Connection reset by peer" log message with some
* versions of gpg-agent. */
static void
bye_gpg_agent(int sd)
{
/* don't bother to check the result of the write, it either worked or it
* didn't, but either way we're closing. */
write(sd, "BYE\n", 4);
close(sd);
}
/* Locate a running GPG Agent, and return an open file descriptor
* for communication with the agent in *NEW_SD. If no running agent
* can be found, set *NEW_SD to -1. */
static svn_error_t *
find_running_gpg_agent(int *new_sd, apr_pool_t *pool)
{
char *buffer;
char *gpg_agent_info = NULL;
const char *socket_name = NULL;
const char *request = NULL;
const char *p = NULL;
char *ep = NULL;
int sd;
*new_sd = -1;
/* This implements the method of finding the socket as described in
* the gpg-agent man page under the --use-standard-socket option.
* The manage page misleadingly says the standard socket is
* "named 'S.gpg-agent' located in the home directory." The standard
* socket path is actually in the .gnupg directory in the home directory,
* i.e. ~/.gnupg/S.gpg-agent */
gpg_agent_info = getenv("GPG_AGENT_INFO");
if (gpg_agent_info != NULL)
{
apr_array_header_t *socket_details;
/* For reference GPG_AGENT_INFO consists of 3 : separated fields.
* The path to the socket, the pid of the gpg-agent process and
* finally the version of the protocol the agent talks. */
socket_details = svn_cstring_split(gpg_agent_info, ":", TRUE,
pool);
socket_name = APR_ARRAY_IDX(socket_details, 0, const char *);
}
else
{
const char *homedir = svn_user_get_homedir(pool);
if (!homedir)
return SVN_NO_ERROR;
socket_name = svn_dirent_join_many(pool, homedir, ".gnupg",
"S.gpg-agent", NULL);
}
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';
src/subversion/subversion/libsvn_subr/gpg_agent.c view on Meta::CPAN
if (connect(sd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
{
close(sd);
return SVN_NO_ERROR;
}
}
else
return SVN_NO_ERROR;
/* Receive the connection status from the gpg-agent daemon. */
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;
}
/* The GPG-Agent documentation says:
* "Clients should deny to access an agent with a socket name which does
* not match its own configuration". */
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;
}
/* The agent will terminate its response with "OK". */
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;
/* Send TTY_NAME to the gpg-agent daemon. */
tty_name = getenv("GPG_TTY");
if (tty_name != NULL)
{
if (!send_option(sd, buf, n, "ttyname", tty_name, scratch_pool))
return FALSE;
}
/* Send TTY_TYPE to the gpg-agent daemon. */
tty_type = getenv("TERM");
if (tty_type != NULL)
{
if (!send_option(sd, buf, n, "ttytype", tty_type, scratch_pool))
return FALSE;
}
/* Compute LC_CTYPE. */
lc_ctype = getenv("LC_ALL");
if (lc_ctype == NULL)
lc_ctype = getenv("LC_CTYPE");
if (lc_ctype == NULL)
lc_ctype = getenv("LANG");
/* Send LC_CTYPE to the gpg-agent daemon. */
if (lc_ctype != NULL)
{
if (!send_option(sd, buf, n, "lc-ctype", lc_ctype, scratch_pool))
return FALSE;
}
/* Send DISPLAY to the gpg-agent daemon. */
display = getenv("DISPLAY");
if (display != NULL)
{
if (!send_option(sd, buf, n, "display", display, scratch_pool))
return FALSE;
}
return TRUE;
}
/* Implementation of svn_auth__password_get_t that retrieves the password
from gpg-agent */
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;
const char *p = NULL;
src/subversion/subversion/libsvn_subr/gpg_agent.c view on Meta::CPAN
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)
/* X means no error to the gpg-agent protocol */
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;
src/subversion/subversion/libsvn_subr/gpg_agent.c view on Meta::CPAN
}
/* Implementation of svn_auth__password_set_t that would store the
password in GPG Agent if that's how this particular integration
worked. But it isn't. GPG Agent stores the password provided by
the user via the pinentry program immediately upon its provision
(and regardless of its accuracy as passwords go), so we just need
to check if a running GPG Agent exists. */
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;
}
/* An implementation of svn_auth_provider_t::first_credentials() */
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;
}
/* An implementation of svn_auth_provider_t::next_credentials() */
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;
/* The users previous credentials failed so first remove the cached entry,
* before trying to retrieve them again. Because gpg-agent stores cached
* credentials immediately upon retrieving them, this gives us the
* opportunity to remove the invalid credentials and prompt the
* user again. While it's possible that server side issues could trigger
* this, this cache is ephemeral so at worst we're just speeding up
* when the user would need to re-enter their password. */
if (svn_hash_gets(parameters, SVN_AUTH_PARAM_NON_INTERACTIVE))
{
/* In this case since we're running non-interactively we do not
* want to clear the cache since the user was never prompted by
* gpg-agent to set a password. */
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;
}
if (strncmp(buffer, "OK\n", 3) != 0)
{
bye_gpg_agent(sd);
return SVN_NO_ERROR;
}
/* TODO: This attempt limit hard codes it at 3 attempts (or 2 retries)
* which matches svn command line client's retry_limit as set in
* svn_cmdline_create_auth_baton(). It would be nice to have that
* limit reflected here but that violates the boundry between the
* prompt provider and the cache provider. gpg-agent is acting as
* both here due to the peculiarties of their design so we'll have to
* live with this for now. Note that when these failures get exceeded
* it'll eventually fall back on the retry limits of whatever prompt
* provider is in effect, so this effectively doubles the limit. */
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;
}
/* An implementation of svn_auth_provider_t::save_credentials() */
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
};
/* Public API */
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 /* SVN_HAVE_GPG_AGENT */
#endif /* !WIN32 */
src/subversion/subversion/po/de.po view on Meta::CPAN
#: ../libsvn_subr/error.c:786
msgid "corrupt data"
msgstr "Fehlerhafte Daten"
# FIXME: Grammar depends on usage!!!!!!! (prepanded by "bei")
#: ../libsvn_subr/error.c:791
msgid "unknown error"
msgstr "Unbekannter Fehler"
#: ../libsvn_subr/gpg_agent.c:358 ../libsvn_subr/prompt.c:631
#, c-format
msgid "Password for '%s': "
msgstr "Passwort für »%s«: "
#: ../libsvn_subr/gpg_agent.c:359
#, c-format
msgid "Enter your Subversion password for %s"
msgstr "Geben Sie Ihr Subversion-Passwort für %s ein"
#: ../libsvn_subr/hash.c:120
msgid "Serialized hash missing terminator"
msgstr "Serialisierte Prüfsumme hat kein Endzeichen"
#: ../libsvn_subr/hash.c:129 ../libsvn_subr/hash.c:142
#: ../libsvn_subr/hash.c:153 ../libsvn_subr/hash.c:165
src/subversion/subversion/po/fr.po view on Meta::CPAN
msgstr "Erreur de version"
#: ../libsvn_subr/error.c:734
msgid "corrupt data"
msgstr "Données corrompues"
#: ../libsvn_subr/error.c:739
msgid "unknown error"
msgstr "Erreur inconnue"
#: ../libsvn_subr/gpg_agent.c:339 ../libsvn_subr/prompt.c:211
#, c-format
msgid "Password for '%s': "
msgstr "Mot de passe pour '%s'Â : "
#: ../libsvn_subr/gpg_agent.c:340
#, c-format
msgid "Enter your Subversion password for %s"
msgstr "Entrez votre mot de passe Subversion pour %s"
#: ../libsvn_subr/hash.c:117
msgid "Serialized hash missing terminator"
msgstr "Hash sérialisé dans fin de chaîne"
#: ../libsvn_subr/hash.c:125 ../libsvn_subr/hash.c:137
#: ../libsvn_subr/hash.c:147 ../libsvn_subr/hash.c:158
src/subversion/subversion/po/subversion.pot view on Meta::CPAN
msgstr ""
#: ../include/svn_error_codes.h:1437
msgid "All authentication providers exhausted"
msgstr ""
#: ../include/svn_error_codes.h:1441
msgid "Credentials not saved"
msgstr ""
#: ../include/svn_error_codes.h:1446 ../libsvn_subr/gpg_agent.c:415
msgid "Authentication failed"
msgstr ""
#: ../include/svn_error_codes.h:1452
msgid "Read access denied for root of edit"
msgstr ""
#: ../include/svn_error_codes.h:1457
msgid "Item is not readable"
msgstr ""
src/subversion/subversion/po/subversion.pot view on Meta::CPAN
msgstr ""
#: ../libsvn_subr/error.c:786
msgid "corrupt data"
msgstr ""
#: ../libsvn_subr/error.c:791
msgid "unknown error"
msgstr ""
#: ../libsvn_subr/gpg_agent.c:408 ../libsvn_subr/prompt.c:631
#, c-format
msgid "Password for '%s': "
msgstr ""
#: ../libsvn_subr/gpg_agent.c:409
#, c-format
msgid "Enter your Subversion password for %s"
msgstr ""
#: ../libsvn_subr/hash.c:120
msgid "Serialized hash missing terminator"
msgstr ""
#: ../libsvn_subr/hash.c:129 ../libsvn_subr/hash.c:142
#: ../libsvn_subr/hash.c:153 ../libsvn_subr/hash.c:165
src/subversion/subversion/po/sv.po view on Meta::CPAN
msgstr "versionsfel"
#: ../libsvn_subr/error.c:786
msgid "corrupt data"
msgstr "trasig data"
#: ../libsvn_subr/error.c:791
msgid "unknown error"
msgstr "okänt fel"
#: ../libsvn_subr/gpg_agent.c:358 ../libsvn_subr/prompt.c:631
#, c-format
msgid "Password for '%s': "
msgstr "Lösenord för \"%s\": "
#: ../libsvn_subr/gpg_agent.c:359
#, c-format
msgid "Enter your Subversion password for %s"
msgstr "Mata in ditt Subversion-lösenord för %s"
#: ../libsvn_subr/hash.c:120
msgid "Serialized hash missing terminator"
msgstr "Serialiserad hashtabell saknar slutmärke"
#: ../libsvn_subr/hash.c:129 ../libsvn_subr/hash.c:142
#: ../libsvn_subr/hash.c:153 ../libsvn_subr/hash.c:165
src/subversion/subversion/po/zh_CN.po view on Meta::CPAN
msgstr "çæ¬é误"
#: ../libsvn_subr/error.c:786
msgid "corrupt data"
msgstr "æåçæ°æ®"
#: ../libsvn_subr/error.c:791
msgid "unknown error"
msgstr "æªç¥é误"
#: ../libsvn_subr/gpg_agent.c:342 ../libsvn_subr/prompt.c:631
#, c-format
msgid "Password for '%s': "
msgstr "â%sâçå¯ç : "
#: ../libsvn_subr/gpg_agent.c:343
#, c-format
msgid "Enter your Subversion password for %s"
msgstr "请为 %s è¾å
¥ Subversion å¯ç "
#: ../libsvn_subr/hash.c:120
msgid "Serialized hash missing terminator"
msgstr "åºåååå¸å¼æ²¡æç»ç»ç¬¦"
#: ../libsvn_subr/hash.c:129 ../libsvn_subr/hash.c:142
#: ../libsvn_subr/hash.c:153 ../libsvn_subr/hash.c:165
src/subversion/tools/dist/_gnupg.py view on Meta::CPAN
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
# EVENT SHALL THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
""" A wrapper for the 'gpg' command::
Portions of this module are derived from A.M. Kuchling's well-designed
GPG.py, using Richard Jones' updated version 1.3, which can be found
in the pycrypto CVS repository on Sourceforge:
http://pycrypto.cvs.sourceforge.net/viewvc/pycrypto/gpg/GPG.py
This module is *not* forward-compatible with amk's; some of the
old interface has changed. For instance, since I've added decrypt
functionality, I elected to initialize with a 'gnupghome' argument
instead of 'keyring', so that gpg can find both the public and secret
keyrings. I've also altered some of the returned objects in order for
the caller to not have to know as much about the internals of the
result classes.
While the rest of ISconf is released under the GPL, I am releasing
this single file under the same terms that A.M. Kuchling used for
pycrypto.
Steve Traugott, stevegt@terraluna.org
Thu Jun 23 21:27:20 PDT 2005
src/subversion/tools/dist/_gnupg.py view on Meta::CPAN
s = s.encode(encoding)
from io import BytesIO
rv = BytesIO(s)
except ImportError:
rv = StringIO(s)
return rv
class Verify(object):
"Handle status messages for --verify"
def __init__(self, gpg):
self.gpg = gpg
self.valid = False
self.fingerprint = self.creation_date = self.timestamp = None
self.signature_id = self.key_id = None
self.username = None
def __nonzero__(self):
return self.valid
__bool__ = __nonzero__
src/subversion/tools/dist/_gnupg.py view on Meta::CPAN
self.status = (('%s %s') % (key[:3], key[3:])).lower()
else:
raise ValueError("Unknown status message: %r" % key)
class ImportResult(object):
"Handle status messages for --import"
counts = '''count no_user_id imported imported_rsa unchanged
n_uids n_subk n_sigs n_revoc sec_read sec_imported
sec_dups not_imported'''.split()
def __init__(self, gpg):
self.gpg = gpg
self.imported = []
self.results = []
self.fingerprints = []
for result in self.counts:
setattr(self, result, None)
def __nonzero__(self):
if self.not_imported: return False
if not self.fingerprints: return False
return True
src/subversion/tools/dist/_gnupg.py view on Meta::CPAN
Don't care about (info from src/DETAILS):
crt = X.509 certificate
crs = X.509 certificate and private key available
sub = subkey (secondary key)
ssb = secret subkey (secondary key)
uat = user attribute (same as user id except for field 10).
sig = signature
rev = revocation signature
pkd = public key data (special field format, see below)
grp = reserved for gpgsm
rvk = revocation key
'''
def __init__(self, gpg):
self.gpg = gpg
self.curkey = None
self.fingerprints = []
self.uids = []
def key(self, args):
vars = ("""
type trust length algo keyid date expires dummy ownertrust uid
""").split()
self.curkey = {}
for i in range(len(vars)):
src/subversion/tools/dist/_gnupg.py view on Meta::CPAN
def uid(self, args):
self.curkey['uids'].append(args[9])
self.uids.append(args[9])
def handle_status(self, key, value):
pass
class Crypt(Verify):
"Handle status messages for --encrypt and --decrypt"
def __init__(self, gpg):
Verify.__init__(self, gpg)
self.data = ''
self.ok = False
self.status = ''
def __nonzero__(self):
if self.ok: return True
return False
__bool__ = __nonzero__
def __str__(self):
return self.data.decode(self.gpg.encoding, self.gpg.decode_errors)
def handle_status(self, key, value):
if key in ("ENC_TO", "USERID_HINT", "GOODMDC", "END_DECRYPTION",
"BEGIN_SIGNING", "NO_SECKEY", "ERROR", "NODATA"):
# in the case of ERROR, this is because a more specific error
# message will have come first
pass
elif key in ("NEED_PASSPHRASE", "BAD_PASSPHRASE", "GOOD_PASSPHRASE",
"MISSING_PASSPHRASE", "DECRYPTION_FAILED",
"KEY_NOT_CREATED"):
src/subversion/tools/dist/_gnupg.py view on Meta::CPAN
self.status = 'key expired'
elif key == "SIG_CREATED":
self.status = 'sig created'
elif key == "SIGEXPIRED":
self.status = 'sig expired'
else:
Verify.handle_status(self, key, value)
class GenKey(object):
"Handle status messages for --gen-key"
def __init__(self, gpg):
self.gpg = gpg
self.type = None
self.fingerprint = None
def __nonzero__(self):
if self.fingerprint: return True
return False
__bool__ = __nonzero__
def __str__(self):
src/subversion/tools/dist/_gnupg.py view on Meta::CPAN
def handle_status(self, key, value):
if key in ("PROGRESS", "GOOD_PASSPHRASE", "NODATA"):
pass
elif key == "KEY_CREATED":
(self.type,self.fingerprint) = value.split()
else:
raise ValueError("Unknown status message: %r" % key)
class DeleteResult(object):
"Handle status messages for --delete-key and --delete-secret-key"
def __init__(self, gpg):
self.gpg = gpg
self.status = 'ok'
def __str__(self):
return self.status
problem_reason = {
'1': 'No such key',
'2': 'Must delete secret key first',
'3': 'Ambigious specification',
}
def handle_status(self, key, value):
if key == "DELETE_PROBLEM":
self.status = self.problem_reason.get(value,
"Unknown error: %r" % value)
else:
raise ValueError("Unknown status message: %r" % key)
class Sign(object):
"Handle status messages for --sign"
def __init__(self, gpg):
self.gpg = gpg
self.type = None
self.fingerprint = None
def __nonzero__(self):
return self.fingerprint is not None
__bool__ = __nonzero__
def __str__(self):
return self.data.decode(self.gpg.encoding, self.gpg.decode_errors)
def handle_status(self, key, value):
if key in ("USERID_HINT", "NEED_PASSPHRASE", "BAD_PASSPHRASE",
"GOOD_PASSPHRASE", "BEGIN_SIGNING"):
pass
elif key == "SIG_CREATED":
(self.type,
algo, hashalgo, cls,
self.timestamp, self.fingerprint
) = value.split()
src/subversion/tools/dist/_gnupg.py view on Meta::CPAN
result_map = {
'crypt': Crypt,
'delete': DeleteResult,
'generate': GenKey,
'import': ImportResult,
'list': ListKeys,
'sign': Sign,
'verify': Verify,
}
"Encapsulate access to the gpg executable"
def __init__(self, gpgbinary='gpg', gnupghome=None, verbose=False,
use_agent=False, keyring=None):
"""Initialize a GPG process wrapper. Options are:
gpgbinary -- full pathname for GPG binary.
gnupghome -- full pathname to where we can find the public and
private keyrings. Default is whatever gpg defaults to.
keyring -- name of alternative keyring file to use. If specified,
the default keyring is not used.
"""
self.gpgbinary = gpgbinary
self.gnupghome = gnupghome
self.keyring = keyring
self.verbose = verbose
self.use_agent = use_agent
self.encoding = locale.getpreferredencoding()
if self.encoding is None: # This happens on Jython!
self.encoding = sys.stdin.encoding
if gnupghome and not os.path.isdir(self.gnupghome):
os.makedirs(self.gnupghome,0x1C0)
p = self._open_subprocess(["--version"])
result = self.result_map['verify'](self) # any result will do for this
self._collect_output(p, result, stdin=p.stdin)
if p.returncode != 0:
raise ValueError("Error invoking gpg: %s: %s" % (p.returncode,
result.stderr))
def _open_subprocess(self, args, passphrase=False):
# Internal method: open a pipe to a GPG subprocess and return
# the file objects for communicating with it.
cmd = [self.gpgbinary, '--status-fd 2 --no-tty']
if self.gnupghome:
cmd.append('--homedir "%s" ' % self.gnupghome)
if self.keyring:
cmd.append('--no-default-keyring --keyring "%s" ' % self.keyring)
if passphrase:
cmd.append('--batch --passphrase-fd 0')
if self.use_agent:
cmd.append('--use-agent')
cmd.extend(args)
cmd = ' '.join(cmd)
src/subversion/tools/dist/_gnupg.py view on Meta::CPAN
return result
def sign_file(self, file, keyid=None, passphrase=None, clearsign=True,
detach=False, binary=False):
"""sign file"""
logger.debug("sign_file: %s", file)
if binary:
args = ['-s']
else:
args = ['-sa']
# You can't specify detach-sign and clearsign together: gpg ignores
# the detach-sign in that case.
if detach:
args.append("--detach-sign")
elif clearsign:
args.append("--clearsign")
if keyid:
args.append('--default-key "%s"' % keyid)
result = self.result_map['sign'](self)
#We could use _handle_io here except for the fact that if the
#passphrase is bad, gpg bails and you can't write the message.
p = self._open_subprocess(args, passphrase is not None)
try:
stdin = p.stdin
if passphrase:
_write_passphrase(stdin, passphrase, self.encoding)
writer = _threaded_copy_data(file, stdin)
except IOError:
logging.exception("error writing message")
writer = None
self._collect_output(p, result, writer, stdin)
return result
def verify(self, data):
"""Verify the signature on the contents of the string 'data'
>>> gpg = GPG(gnupghome="keys")
>>> input = gpg.gen_key_input(Passphrase='foo')
>>> key = gpg.gen_key(input)
>>> assert key
>>> sig = gpg.sign('hello',keyid=key.fingerprint,passphrase='bar')
>>> assert not sig
>>> sig = gpg.sign('hello',keyid=key.fingerprint,passphrase='foo')
>>> assert sig
>>> verify = gpg.verify(sig.data)
>>> assert verify
"""
f = _make_binary_stream(data, self.encoding)
result = self.verify_file(f)
f.close()
return result
def verify_file(self, file, data_filename=None):
"Verify the signature on the contents of the file-like object 'file'"
logger.debug('verify_file: %r, %r', file, data_filename)
result = self.result_map['verify'](self)
args = ['--verify']
if data_filename is None:
self._handle_io(args, file, result, binary=True)
else:
logger.debug('Handling detached verification')
import tempfile
fd, fn = tempfile.mkstemp(prefix='pygpg')
s = file.read()
file.close()
logger.debug('Wrote to temp file: %r', s)
os.write(fd, s)
os.close(fd)
args.append(fn)
args.append('"%s"' % data_filename)
try:
p = self._open_subprocess(args)
self._collect_output(p, result, stdin=p.stdin)
src/subversion/tools/dist/_gnupg.py view on Meta::CPAN
#
# KEY MANAGEMENT
#
def import_keys(self, key_data):
""" import the key_data into our keyring
>>> import shutil
>>> shutil.rmtree("keys")
>>> gpg = GPG(gnupghome="keys")
>>> input = gpg.gen_key_input()
>>> result = gpg.gen_key(input)
>>> print1 = result.fingerprint
>>> result = gpg.gen_key(input)
>>> print2 = result.fingerprint
>>> pubkey1 = gpg.export_keys(print1)
>>> seckey1 = gpg.export_keys(print1,secret=True)
>>> seckeys = gpg.list_keys(secret=True)
>>> pubkeys = gpg.list_keys()
>>> assert print1 in seckeys.fingerprints
>>> assert print1 in pubkeys.fingerprints
>>> str(gpg.delete_keys(print1))
'Must delete secret key first'
>>> str(gpg.delete_keys(print1,secret=True))
'ok'
>>> str(gpg.delete_keys(print1))
'ok'
>>> str(gpg.delete_keys("nosuchkey"))
'No such key'
>>> seckeys = gpg.list_keys(secret=True)
>>> pubkeys = gpg.list_keys()
>>> assert not print1 in seckeys.fingerprints
>>> assert not print1 in pubkeys.fingerprints
>>> result = gpg.import_keys('foo')
>>> assert not result
>>> result = gpg.import_keys(pubkey1)
>>> pubkeys = gpg.list_keys()
>>> seckeys = gpg.list_keys(secret=True)
>>> assert not print1 in seckeys.fingerprints
>>> assert print1 in pubkeys.fingerprints
>>> result = gpg.import_keys(seckey1)
>>> assert result
>>> seckeys = gpg.list_keys(secret=True)
>>> pubkeys = gpg.list_keys()
>>> assert print1 in seckeys.fingerprints
>>> assert print1 in pubkeys.fingerprints
>>> assert print2 in pubkeys.fingerprints
"""
result = self.result_map['import'](self)
logger.debug('import_keys: %r', key_data[:256])
data = _make_binary_stream(key_data, self.encoding)
self._handle_io(['--import'], data, result, binary=True)
logger.debug('import_keys result: %r', result.__dict__)
data.close()
return result
def recv_keys(self, keyserver, *keyids):
"""Import a key from a keyserver
>>> import shutil
>>> shutil.rmtree("keys")
>>> gpg = GPG(gnupghome="keys")
>>> result = gpg.recv_keys('pgp.mit.edu', '3FF0DB166A7476EA')
>>> assert result
"""
result = self.result_map['import'](self)
logger.debug('recv_keys: %r', keyids)
data = _make_binary_stream("", self.encoding)
#data = ""
args = ['--keyserver', keyserver, '--recv-keys']
args.extend(keyids)
self._handle_io(args, data, result, binary=True)
src/subversion/tools/dist/_gnupg.py view on Meta::CPAN
which='secret-key'
if _is_sequence(fingerprints):
fingerprints = ' '.join(fingerprints)
args = ['--batch --delete-%s "%s"' % (which, fingerprints)]
result = self.result_map['delete'](self)
p = self._open_subprocess(args)
self._collect_output(p, result, stdin=p.stdin)
return result
def export_keys(self, keyids, secret=False):
"export the indicated keys. 'keyid' is anything gpg accepts"
which=''
if secret:
which='-secret-key'
if _is_sequence(keyids):
keyids = ' '.join(['"%s"' % k for k in keyids])
args = ["--armor --export%s %s" % (which, keyids)]
p = self._open_subprocess(args)
# gpg --export produces no status-fd output; stdout will be
# empty in case of failure
#stdout, stderr = p.communicate()
result = self.result_map['delete'](self) # any result will do
self._collect_output(p, result, stdin=p.stdin)
logger.debug('export_keys result: %r', result.data)
return result.data.decode(self.encoding, self.decode_errors)
def list_keys(self, secret=False):
""" list the keys currently in the keyring
>>> import shutil
>>> shutil.rmtree("keys")
>>> gpg = GPG(gnupghome="keys")
>>> input = gpg.gen_key_input()
>>> result = gpg.gen_key(input)
>>> print1 = result.fingerprint
>>> result = gpg.gen_key(input)
>>> print2 = result.fingerprint
>>> pubkeys = gpg.list_keys()
>>> assert print1 in pubkeys.fingerprints
>>> assert print2 in pubkeys.fingerprints
"""
which='keys'
if secret:
which='secret-keys'
args = "--list-%s --fixed-list-mode --fingerprint --with-colons" % (which,)
args = [args]
src/subversion/tools/dist/_gnupg.py view on Meta::CPAN
continue
keyword = L[0]
if keyword in valid_keywords:
getattr(result, keyword)(L)
return result
def gen_key(self, input):
"""Generate a key; you might use gen_key_input() to create the
control input.
>>> gpg = GPG(gnupghome="keys")
>>> input = gpg.gen_key_input()
>>> result = gpg.gen_key(input)
>>> assert result
>>> result = gpg.gen_key('foo')
>>> assert not result
"""
args = ["--gen-key --batch"]
result = self.result_map['generate'](self)
f = _make_binary_stream(input, self.encoding)
self._handle_io(args, f, result, binary=True)
f.close()
return result
def gen_key_input(self, **kwargs):
"""
Generate --gen-key input per gpg doc/DETAILS
"""
parms = {}
for key, val in list(kwargs.items()):
key = key.replace('_','-').title()
parms[key] = val
parms.setdefault('Key-Type','RSA')
parms.setdefault('Key-Length',1024)
parms.setdefault('Name-Real', "Autogenerated Key")
parms.setdefault('Name-Comment', "Generated by gnupg.py")
try:
src/subversion/tools/dist/_gnupg.py view on Meta::CPAN
self._handle_io(args, file, result, passphrase=passphrase, binary=True)
logger.debug('encrypt result: %r', result.data)
return result
def encrypt(self, data, recipients, **kwargs):
"""Encrypt the message contained in the string 'data'
>>> import shutil
>>> if os.path.exists("keys"):
... shutil.rmtree("keys")
>>> gpg = GPG(gnupghome="keys")
>>> input = gpg.gen_key_input(passphrase='foo')
>>> result = gpg.gen_key(input)
>>> print1 = result.fingerprint
>>> input = gpg.gen_key_input()
>>> result = gpg.gen_key(input)
>>> print2 = result.fingerprint
>>> result = gpg.encrypt("hello",print2)
>>> message = str(result)
>>> assert message != 'hello'
>>> result = gpg.decrypt(message)
>>> assert result
>>> str(result)
'hello'
>>> result = gpg.encrypt("hello again",print1)
>>> message = str(result)
>>> result = gpg.decrypt(message)
>>> result.status == 'need passphrase'
True
>>> result = gpg.decrypt(message,passphrase='bar')
>>> result.status in ('decryption failed', 'bad passphrase')
True
>>> assert not result
>>> result = gpg.decrypt(message,passphrase='foo')
>>> result.status == 'decryption ok'
True
>>> str(result)
'hello again'
>>> result = gpg.encrypt("signed hello",print2,sign=print1)
>>> result.status == 'need passphrase'
True
>>> result = gpg.encrypt("signed hello",print2,sign=print1,passphrase='foo')
>>> result.status == 'encryption ok'
True
>>> message = str(result)
>>> result = gpg.decrypt(message)
>>> result.status == 'decryption ok'
True
>>> assert result.fingerprint == print1
"""
data = _make_binary_stream(data, self.encoding)
result = self.encrypt_file(data, recipients, **kwargs)
data.close()
return result
src/subversion/tools/dist/collect_sigs.py view on Meta::CPAN
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
#
# A script intended to be useful in helping to collect signatures for a
# release. This is a pretty rough, and patches are welcome to improve it.
#
# Some thoughts about future improvement:
# * Display of per-file and per-release statistics
# * Make use of the python-gpg package (http://code.google.com/p/python-gnupg/)
# * Post to IRC when a new signature is collected
# - Since we don't want to have a long running bot, perhaps we could
# also patch wayita to accept and then echo a privmsg?
# * Mail dev@ when somebody submits a successful signature, and include a
# comments field which could be included in the mail.
# * Use a subversion repository instead of sqlite backend
# - no need to re-invent storage and retrieval
# - perhaps we could re-use existing CIA/mailer hooks?
#
src/subversion/tools/dist/collect_sigs.py view on Meta::CPAN
return (template % lines) + signature_area
def save_valid_sig(db, filename, keyid, signature):
db.execute('INSERT OR REPLACE INTO signatures VALUES (?,?,?);',
(keyid, filename, buffer(signature)))
db.commit()
generate_asc_files(config.sigdir)
def verify_sig_for_file(signature, filename):
args = ['gpg', '--logger-fd', '1', '--no-tty',
'--status-fd', '2', '--verify', '-',
os.path.join(config.filesdir, filename)]
gpg = subprocess.Popen(args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
gpg.stdin.write(signature)
gpg.stdin.close()
rc = gpg.wait()
output = gpg.stdout.read()
status = gpg.stderr.read()
if rc:
return (False, status + output)
lines = status.split('\n')
for line in lines:
match = r.search(line)
if match:
keyid = match.group(1)
user = match.group(2)
src/subversion/tools/dist/dist.sh view on Meta::CPAN
echo "Rolling $DISTNAME.zip ..."
(cd "$DIST_SANDBOX" > /dev/null && zip -q -r - "$DISTNAME") > \
"$DISTNAME.zip"
fi
echo "Removing sandbox..."
rm -rf "$DIST_SANDBOX"
sign_file()
{
if [ -n "$SIGN" ]; then
type gpg > /dev/null 2>&1
if [ $? -eq 0 ]; then
if test -n "$user"; then
args="--default-key $user"
fi
for ARG in $@
do
gpg --armor $args --detach-sign $ARG
done
else
type pgp > /dev/null 2>&1
if [ $? -eq 0 ]; then
if test -n "$user"; then
args="-u $user"
fi
for ARG in $@
do
pgp -sba $ARG $args
src/subversion/tools/dist/release.py view on Meta::CPAN
#----------------------------------------------------------------------
# Sign the candidate release artifacts
def sign_candidates(args):
'Sign candidate artifacts in the dist development directory.'
def sign_file(filename):
asc_file = open(filename + '.asc', 'a')
logging.info("Signing %s" % filename)
proc = subprocess.Popen(['gpg', '-ba', '-o', '-', filename],
stdout=asc_file)
proc.wait()
asc_file.close()
if args.target:
target = args.target
else:
target = get_deploydir(args.base_dir)
for e in extns:
src/subversion/tools/dist/release.py view on Meta::CPAN
key_start = '-----BEGIN PGP SIGNATURE-----'
fp_pattern = re.compile(r'^pub\s+(\w+\/\w+)[^\n]*\n\s+Key\sfingerprint\s=((\s+[0-9A-F]{4}){10})\nuid\s+([^<\(]+)\s')
def get_siginfo(args, quiet=False):
'Returns a list of signatures for the release.'
try:
import gnupg
except ImportError:
import _gnupg as gnupg
gpg = gnupg.GPG()
if args.target:
target = args.target
else:
target = get_deploydir(args.base_dir)
good_sigs = {}
fingerprints = {}
output = []
src/subversion/tools/dist/release.py view on Meta::CPAN
for filename in glob.glob(glob_pattern):
text = open(filename).read()
keys = text.split(key_start)
if not quiet:
logging.info("Checking %d sig(s) in %s" % (len(keys[1:]), filename))
for key in keys[1:]:
fd, fn = tempfile.mkstemp()
os.write(fd, key_start + key)
os.close(fd)
verified = gpg.verify_file(open(fn, 'rb'), filename[:-4])
os.unlink(fn)
if verified.valid:
good_sigs[verified.key_id[-8:]] = True
else:
sys.stderr.write("BAD SIGNATURE for %s\n" % filename)
if verified.key_id:
sys.stderr.write(" key id: %s\n" % verified.key_id)
sys.exit(1)
for id in good_sigs.keys():
gpg = subprocess.Popen(['gpg', '--fingerprint', id],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
rc = gpg.wait()
gpg_output = gpg.stdout.read()
if rc:
print(gpg_output)
sys.stderr.write("UNABLE TO GET FINGERPRINT FOR %s" % id)
sys.exit(1)
gpg_output = "\n".join([ l for l in gpg_output.splitlines()
if l[0:7] != 'Warning' ])
fp = fp_pattern.match(gpg_output).groups()
fingerprints["%s [%s] %s" % (fp[3], fp[0], fp[1])] = fp
for entry in sorted(fingerprints.keys()):
fp = fingerprints[entry]
output.append(" %s [%s] with fingerprint:" % (fp[3], fp[0]))
output.append(" %s" % fp[1])
return output
def check_sigs(args):
'Check the signatures for the release.'
output = get_siginfo(args)
for line in output:
print(line)
def get_keys(args):
'Import the LDAP-based KEYS file to gpg'
# We use a tempfile because urlopen() objects don't have a .fileno()
with tempfile.SpooledTemporaryFile() as fd:
fd.write(urllib2.urlopen(KEYS).read())
fd.flush()
fd.seek(0)
subprocess.check_call(['gpg', '--import'], stdin=fd)
#----------------------------------------------------------------------
# Main entry point for argument parsing and handling
def main():
'Parse arguments, and drive the appropriate subcommand.'
# Setup our main parser
parser = argparse.ArgumentParser(
description='Create an Apache Subversion release.')
src/subversion/tools/dist/release.py view on Meta::CPAN
release''')
subparser.set_defaults(func=check_sigs)
subparser.add_argument('version', type=Version,
help='''The release label, such as '1.7.0-alpha1'.''')
subparser.add_argument('--target',
help='''The full path to the directory containing
release artifacts.''')
# get-keys
subparser = subparsers.add_parser('get-keys',
help='''Import committers' public keys to ~/.gpg/''')
subparser.set_defaults(func=get_keys)
# A meta-target
subparser = subparsers.add_parser('clean',
help='''The same as the '--clean' switch, but as a
separate subcommand.''')
subparser.set_defaults(func=cleanup)
# Parse the arguments
args = parser.parse_args()